diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..efa748a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.vscode +.husky +.github +.cspell +.env +node_modules \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a0b0942 --- /dev/null +++ b/.env.example @@ -0,0 +1,30 @@ +# THIS FILE SHOULD BE INSIDE OF build/ + +# Discord +DISCORD_TOKEN="" +DISCORD_CLIENT_ID="" +DISCORD_GUILD_ID="" + +# Database +DATABASE_URL="file:./production.db" + +# Encryption +ENCRYPTION_ALGORITHM="aes-256-ctr" +ENCRYPTION_SECRET="A RANDOM STRING WITH LENGTH OF 32" + +#Embed +EMBED_COLOR_SUCCESS="#22bb33" +EMBED_COLOR_WAIT="#f0ad4e" +EMBED_COLOR_ERROR="#bb2124" +EMBED_FOOTER_TEXT="https://github.com/ZynerOrg/xyter" +EMBED_FOOTER_ICON="https://github.com/ZynerOrg.png" + +# Log +LOG_LEVEL="silly" + +# Reputation +REPUTATION_TIMEOUT="86400" + +# Bot Hoster +BOT_HOSTER_NAME="Zyner" +BOT_HOSTER_URL="https://xyter.zyner.org/customization/change-hoster" diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 76add87..0000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -dist \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 39d4adc..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "plugins": ["@typescript-eslint", "no-loops", "prettier"], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "prettier" - ], - "rules": { - "no-console": 1, - "no-loops/no-loops": 2 - } -} diff --git a/.github/ISSUE_TEMPLATE/command_request.yaml b/.github/ISSUE_TEMPLATE/command_request.yaml index e6d047c..bb03656 100644 --- a/.github/ISSUE_TEMPLATE/command_request.yaml +++ b/.github/ISSUE_TEMPLATE/command_request.yaml @@ -42,7 +42,7 @@ body: id: terms attributes: label: Code of Conduct - description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com) + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/ZynerOrg/xyter/blob/main/CODE_OF_CONDUCT.md) options: - label: I agree to follow this project's Code of Conduct required: true diff --git a/.husky/.gitignore b/.husky/.gitignore deleted file mode 100644 index 31354ec..0000000 --- a/.husky/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_ diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100644 index c37466e..0000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -npx lint-staged \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index a17e74d..65197a8 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -45,7 +45,8 @@ "sonarsource.sonarlint-vscode", "nicoespeon.hocus-pocus", "aaron-bond.better-comments", - "oouo-diogo-perdigao.docthis" + "oouo-diogo-perdigao.docthis", + "Vtrois.gitmoji-vscode" ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. "unwantedRecommendations": [] diff --git a/.vscode/settings.json b/.vscode/settings.json index 38d1af9..410753e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,7 +4,6 @@ "editor.cursorBlinking": "phase", "editor.cursorSmoothCaretAnimation": true, "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.fontFamily": "Cascadia Code", "editor.fontLigatures": true, "editor.formatOnSave": true, "editor.guides.bracketPairs": "active", @@ -15,6 +14,9 @@ "editor.wordWrapColumn": 100, "files.eol": "\n", "files.trimTrailingWhitespace": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true + }, "cSpell.customDictionaries": { "custom-dictionary-workspace": { "name": "custom-dictionary-workspace", @@ -22,5 +24,11 @@ "addWords": true, "scope": "workspace" } + }, + "[dotenv]": { + "editor.defaultFormatter": "foxundermoon.shell-format" + }, + "[prisma]": { + "editor.defaultFormatter": "Prisma.prisma" } } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..172cf74 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM node:19 + +LABEL maintainer="xyter@zyner.org" + +WORKDIR /build + +COPY package* . +RUN npm install + +COPY . . + +RUN npx -y tsc + +WORKDIR /app + +RUN cp -r /build/build/* . +RUN cp -r /build/node_modules . + +CMD [ "node", "." ] diff --git a/PRIVACY.md b/PRIVACY.md deleted file mode 100644 index 4bffac4..0000000 --- a/PRIVACY.md +++ /dev/null @@ -1,57 +0,0 @@ -> Please note that this is our first privacy policy, so some stuff may be off or not in it! -> We are trying our best to follow the privacy policy law. - -# Privacy Policy - -This document entails the privacy policy and agreement that you accept when adding Xyter bot to a server, or as a member of such a server. This document does not supersede the [Developer Terms of Service](https://discordapp.com/developers/docs/legal). - -This privacy policy applies to **Xyter**#7721 (949998000401436673) - -### Terminology - -- **Server Manager** - Anyone who has the ability to add a bot to a server or configure the bot for the server. This is usually an administrator or moderator -- **Server Member** - Anyone who is a member of server to which one of the bots has been added -- **Service User** - Anyone who authorizes an application (logs in) for a scope that provides additional information - -### Data Collected By Command - -The following items may be collected and stored when intentionally provided by a user (usually by means of a command). This data will not be collected automatically. When providing data in this way, you forego any rights to the content of the data provided. - -- Server configurations (/settings guild) -- Data and content for automated tasks (/shop roles) -- Locale (/settings user language) -- Reputation (/reputation give) - -### Data Collected When Enabled - -These items will be automatically collected if a bot is configured to perform certain actions by a server manager. These features are always opt-in, and thus this data will not be collected unless the corresponding feature is enabled. - -- API Token & URL (/settings guild pterodactyl) -- Counters (/admin counter) - -### Data Collected Automatically - -This data may be collected automatically. This data is used to provide statistics or history data. For any bots that collect this data, it is necessary for features of said bot. - -- Username ID (Unique identifier) -- Guild ID (Unique identifier) -- Any data needed for standard operation of Discord bots, such as server permissions -- Credits (/profile view) -- Points (/profile view) -- Levels (/profile view) -- Timestamps (Audit logs and all your data that we store) - -### Data Storage - -All stored data is kept on protected servers. While storage methods vary, most data is kept within password-protected databases such as [MongoDB Atlas](https://atlas.mongodb.com/). Please keep in mind that even with these protections, no data can ever be 100% secure. All efforts are taken to keep your data secure and private, but its absolute security cannot be guaranteed. - -### Feedback - -Feedback on Xyter bot and service is appreciated. When you submit comments, suggestions, bug reports, and any other forms of feedback, you forego any rights to the content, title, or intent of the provided feedback. Additionally, the feedback may be utilized in any way. - -### Agreement - -By adding Xyter bot to your server or using this bot or service in any way, you are consenting to the policies outlined in this document. In addition, you (the server manager) are agreeing to inform your members of the [Developer Terms of Service](https://discordapp.com/developers/docs/legal) and the contents of this document. If you, the server manager, do not agree to this document, you may remove the bot(s) from the server. If you, the server member, do not agree to this document, you may leave the server that contains the bot(s). If you, the service user, do not agree to this document, you may revoke authorization of the application(s) in your 'Authorized Apps' menu. - -Changes to This Privacy Policy -We may update our Privacy Policy from time to time. Thus, we advise you to review this page periodically for any changes. We will notify you of any changes by posting the new Privacy Policy on this page. These changes are effective immediately, after they are posted on this page. diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 4933326..0000000 --- a/SECURITY.md +++ /dev/null @@ -1,14 +0,0 @@ -# Security Policy - -## Supported Versions - -| Version | Supported | -| ---------- | ------------------ | -| 2022.4.x | :white_check_mark: | -| < 2022.4.x | :x: | - -## Reporting a Vulnerability - -Report a security issue to Vermium#9649 on discord or vermium@zyner.org - -I will try to fix the vulnerability within a month, often in some days only. If vulnerability is declined then still don't abuse it in any way you can. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3309d8f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,39 @@ +version: "3" + +services: + app: + image: zyner/xyter:latest + restart: unless-stopped + # build: . + stdin_open: true + tty: true + volumes: + - ./logs:/app/logs + environment: + - DISCORD_TOKEN=DISCORD_TOKEN + - DISCORD_CLIENT_ID=DISCORD_CLIENT_ID + - DISCORD_GUILD_ID=DISCORD_GUILD_ID + - MONGO_URL=mongodb://MONGO_USER:MONGO_PASS@mongodb:27017/admin?retryWrites=true&w=majority + - ENCRYPTION_ALGORITHM=ENCRYPTION_ALGORITHM + - ENCRYPTION_SECRET=ENCRYPTION_SECRET + - EMEBD_COLOR_SUCCESS=EMEBD_COLOR_SUCCESS + - EMBED_COLOR_WAIT=EMBED_COLOR_WAIT + - EMBED_COLOR_ERROR=EMBED_COLOR_ERROR + - EMBED_FOOTER_TEXT=EMBED_FOOTER_TEXT + - EMBED_FOOTER_ICON=EMBED_FOOTER_ICON + - LOG_LEVEL=LOG_LEVEL + - REPUTATION_TIMEOUT=REPUTATION_TIMEOUT + - BOT_HOSTER_NAME=BOT_HOSTER_NAME + - BOT_HOSTER_URL=BOT_HOSTER_URL + - NODE_ENV=production + depends_on: + - mongodb + + mongodb: + image: mongo:latest + restart: unless-stopped + environment: + MONGO_INITDB_ROOT_USERNAME: MONGO_USER + MONGO_INITDB_ROOT_PASSWORD: MONGO_PASS + volumes: + - ./database:/data/db diff --git a/package.json b/package.json index ac149cc..7d67f33 100644 --- a/package.json +++ b/package.json @@ -21,23 +21,27 @@ "url": "https://github.com/ZynerOrg/xyter.git" }, "author": "Vermium Sifell (https://zyner.org)", + "contributors": [ + "Joshua Schmitt (https://jqshuv.xyz)" + ], "license": "GPL-3.0-only", "bugs": { "url": "https://github.com/ZynerOrg/xyter/issues", "email": "vermium@zyner.org" }, "dependencies": { - "@discordjs/builders": "^0.15.0", - "@discordjs/rest": "^0.5.0", + "@discordjs/rest": "^1.0.0", + "@prisma/client": "^4.5.0", "@types/i18next-fs-backend": "^1.1.2", - "axios": "^0.27.2", + "axios": "^1.0.0", "chance": "^1.1.8", "common": "^0.2.5", "crypto": "^1.0.1", - "discord-api-types": "^0.34.0", - "discord.js": "^13.6.0", + "discord-api-types": "^0.37.0", + "discord.js": "^14.0.0", + "dotenv": "^16.0.1", "i18n": "^0.15.0", - "i18next": "^21.6.13", + "i18next": "^22.0.0", "i18next-async-backend": "^2.0.0", "i18next-fs-backend": "^1.1.4", "i18next-http-backend": "^1.4.0", @@ -46,27 +50,28 @@ "node-schedule": "^2.1.0", "ts-node": "^10.7.0", "tsconfig-paths": "^4.0.0", - "typescript": "^4.6.3", - "uuid": "^8.3.2", + "typescript": "^4.8.4", + "uuid": "^9.0.0", "winston-daily-rotate-file": "^4.6.1" }, "devDependencies": { - "@types/chance": "^1.1.3", + "@types/chance": "1.1.3", "@types/node-schedule": "2.1.0", - "@types/uuid": "^8.3.4", - "@typescript-eslint/eslint-plugin": "^5.15.0", - "@typescript-eslint/parser": "^5.15.0", - "eslint": "8.18.0", + "@types/uuid": "8.3.4", + "@typescript-eslint/eslint-plugin": "5.40.1", + "@typescript-eslint/parser": "5.40.1", + "eslint": "8.25.0", "eslint-config-airbnb-base": "15.0.0", "eslint-config-prettier": "8.5.0", "eslint-plugin-import": "2.26.0", - "eslint-plugin-no-loops": "^0.3.0", - "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-no-loops": "0.3.0", + "eslint-plugin-prettier": "4.2.1", "husky": "8.0.1", - "jest": "28.1.1", - "lint-staged": "13.0.1", - "nodemon": "^2.0.16", - "prettier": "^2.6.0" + "jest": "29.2.1", + "lint-staged": "13.0.3", + "nodemon": "2.0.20", + "prettier": "2.7.1", + "prisma": "4.5.0" }, "lint-staged": { "*.ts": "eslint --cache --fix" diff --git a/prisma/.gitignore b/prisma/.gitignore new file mode 100644 index 0000000..87f333a --- /dev/null +++ b/prisma/.gitignore @@ -0,0 +1,2 @@ +*.db +*.db-journal diff --git a/prisma/migrations/20221019063840_init/migration.sql b/prisma/migrations/20221019063840_init/migration.sql new file mode 100644 index 0000000..d7577f0 --- /dev/null +++ b/prisma/migrations/20221019063840_init/migration.sql @@ -0,0 +1,29 @@ +-- CreateTable +CREATE TABLE "Guild" ( + "id" TEXT NOT NULL +); + +-- CreateTable + +CREATE TABLE "User" ( + "id" TEXT NOT NULL +); + +-- CreateTable + +CREATE TABLE "GuildMember" ( + "userId" TEXT NOT NULL, + "guildId" TEXT NOT NULL +); + +-- CreateIndex + +CREATE UNIQUE INDEX "Guild_id_key" ON "Guild" ("id"); + +-- CreateIndex + +CREATE UNIQUE INDEX "User_id_key" ON "User" ("id"); + +-- CreateIndex + +CREATE UNIQUE INDEX "GuildMember_userId_guildId_key" ON "GuildMember" ("userId", "guildId"); diff --git a/prisma/migrations/20221019064232_init/migration.sql b/prisma/migrations/20221019064232_init/migration.sql new file mode 100644 index 0000000..2028506 --- /dev/null +++ b/prisma/migrations/20221019064232_init/migration.sql @@ -0,0 +1,26 @@ +-- RedefineTables +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_GuildMember" ( + "userId" TEXT NOT NULL, + "guildId" TEXT NOT NULL, + CONSTRAINT "GuildMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "GuildMember_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_GuildMember" ("guildId", "userId") +SELECT + "guildId", + "userId" +FROM + "GuildMember"; + +DROP TABLE "GuildMember"; + +ALTER TABLE "new_GuildMember" RENAME TO "GuildMember"; + +CREATE UNIQUE INDEX "GuildMember_userId_guildId_key" ON "GuildMember" ("userId", "guildId"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221019081114_modules/migration.sql b/prisma/migrations/20221019081114_modules/migration.sql new file mode 100644 index 0000000..d20c517 --- /dev/null +++ b/prisma/migrations/20221019081114_modules/migration.sql @@ -0,0 +1,71 @@ +-- AlterTable +ALTER TABLE "GuildMember" + ADD COLUMN "creditsEarned" INTEGER; + +ALTER TABLE "GuildMember" + ADD COLUMN "pointsEarned" INTEGER; + +-- CreateTable + +CREATE TABLE "GuildCounter" ( + "guildId" TEXT NOT NULL, + "channelId" TEXT NOT NULL, + "word" TEXT NOT NULL, + "counter" INTEGER NOT NULL DEFAULT 0, + CONSTRAINT "GuildCounter_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- RedefineTables + +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_Guild" ( + "id" TEXT NOT NULL, + "creditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "creditsRate" INTEGER NOT NULL DEFAULT 1, + "creditsTimeout" INTEGER NOT NULL DEFAULT 5, + "creditsWorkRate" INTEGER NOT NULL DEFAULT 25, + "creditsWorkTimeout" INTEGER NOT NULL DEFAULT 86400, + "pointsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "pointsRate" INTEGER NOT NULL DEFAULT 1, + "pointsTimeout" INTEGER NOT NULL DEFAULT 5, + "reputationsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "countersEnabled" BOOLEAN NOT NULL DEFAULT FALSE +); + +INSERT INTO "new_Guild" ("id") +SELECT + "id" +FROM + "Guild"; + +DROP TABLE "Guild"; + +ALTER TABLE "new_Guild" RENAME TO "Guild"; + +CREATE UNIQUE INDEX "Guild_id_key" ON "Guild" ("id"); + +CREATE TABLE "new_User" ( + "id" TEXT NOT NULL, + "reputationsEarned" INTEGER NOT NULL DEFAULT 0 +); + +INSERT INTO "new_User" ("id") +SELECT + "id" +FROM + "User"; + +DROP TABLE "User"; + +ALTER TABLE "new_User" RENAME TO "User"; + +CREATE UNIQUE INDEX "User_id_key" ON "User" ("id"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; + +-- CreateIndex + +CREATE UNIQUE INDEX "GuildCounter_guildId_channelId_key" ON "GuildCounter" ("guildId", "channelId"); diff --git a/prisma/migrations/20221019081816_modules/migration.sql b/prisma/migrations/20221019081816_modules/migration.sql new file mode 100644 index 0000000..d2fcde5 --- /dev/null +++ b/prisma/migrations/20221019081816_modules/migration.sql @@ -0,0 +1,35 @@ +/* + Warnings: + + - You are about to drop the column `counter` on the `GuildCounter` table. All the data in the column will be lost. + - You are about to drop the column `word` on the `GuildCounter` table. All the data in the column will be lost. + - Added the required column `triggerWord` to the `GuildCounter` table without a default value. This is not possible if the table is not empty. + */ +-- RedefineTables + +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_GuildCounter" ( + "guildId" TEXT NOT NULL, + "channelId" TEXT NOT NULL, + "triggerWord" TEXT NOT NULL, + "count" INTEGER NOT NULL DEFAULT 0, + CONSTRAINT "GuildCounter_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_GuildCounter" ("channelId", "guildId") +SELECT + "channelId", + "guildId" +FROM + "GuildCounter"; + +DROP TABLE "GuildCounter"; + +ALTER TABLE "new_GuildCounter" RENAME TO "GuildCounter"; + +CREATE UNIQUE INDEX "GuildCounter_guildId_channelId_key" ON "GuildCounter" ("guildId", "channelId"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221019113258_modules/migration.sql b/prisma/migrations/20221019113258_modules/migration.sql new file mode 100644 index 0000000..a22b2a3 --- /dev/null +++ b/prisma/migrations/20221019113258_modules/migration.sql @@ -0,0 +1,43 @@ +-- RedefineTables +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_Guild" ( + "id" TEXT NOT NULL, + "creditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "creditsRate" INTEGER NOT NULL DEFAULT 1, + "creditsTimeout" INTEGER NOT NULL DEFAULT 5, + "creditsWorkRate" INTEGER NOT NULL DEFAULT 25, + "creditsWorkTimeout" INTEGER NOT NULL DEFAULT 86400, + "creditsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "pointsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "pointsRate" INTEGER NOT NULL DEFAULT 1, + "pointsTimeout" INTEGER NOT NULL DEFAULT 5, + "reputationsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "countersEnabled" BOOLEAN NOT NULL DEFAULT FALSE +); + +INSERT INTO "new_Guild" ("countersEnabled", "creditsEnabled", "creditsRate", "creditsTimeout", "creditsWorkRate", "creditsWorkTimeout", "id", "pointsEnabled", "pointsRate", "pointsTimeout", "reputationsEnabled") +SELECT + "countersEnabled", + "creditsEnabled", + "creditsRate", + "creditsTimeout", + "creditsWorkRate", + "creditsWorkTimeout", + "id", + "pointsEnabled", + "pointsRate", + "pointsTimeout", + "reputationsEnabled" +FROM + "Guild"; + +DROP TABLE "Guild"; + +ALTER TABLE "new_Guild" RENAME TO "Guild"; + +CREATE UNIQUE INDEX "Guild_id_key" ON "Guild" ("id"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221019114543_modules/migration.sql b/prisma/migrations/20221019114543_modules/migration.sql new file mode 100644 index 0000000..0d89d31 --- /dev/null +++ b/prisma/migrations/20221019114543_modules/migration.sql @@ -0,0 +1,45 @@ +-- RedefineTables +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_Guild" ( + "id" TEXT NOT NULL, + "creditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "creditsRate" INTEGER NOT NULL DEFAULT 1, + "creditsTimeout" INTEGER NOT NULL DEFAULT 5, + "creditsWorkRate" INTEGER NOT NULL DEFAULT 25, + "creditsWorkTimeout" INTEGER NOT NULL DEFAULT 86400, + "creditsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "pointsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "pointsRate" INTEGER NOT NULL DEFAULT 1, + "pointsTimeout" INTEGER NOT NULL DEFAULT 5, + "pointsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "reputationsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "countersEnabled" BOOLEAN NOT NULL DEFAULT FALSE +); + +INSERT INTO "new_Guild" ("countersEnabled", "creditsEnabled", "creditsMinimumLength", "creditsRate", "creditsTimeout", "creditsWorkRate", "creditsWorkTimeout", "id", "pointsEnabled", "pointsRate", "pointsTimeout", "reputationsEnabled") +SELECT + "countersEnabled", + "creditsEnabled", + "creditsMinimumLength", + "creditsRate", + "creditsTimeout", + "creditsWorkRate", + "creditsWorkTimeout", + "id", + "pointsEnabled", + "pointsRate", + "pointsTimeout", + "reputationsEnabled" +FROM + "Guild"; + +DROP TABLE "Guild"; + +ALTER TABLE "new_Guild" RENAME TO "Guild"; + +CREATE UNIQUE INDEX "Guild_id_key" ON "Guild" ("id"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221019142757_modules/migration.sql b/prisma/migrations/20221019142757_modules/migration.sql new file mode 100644 index 0000000..387fb6e --- /dev/null +++ b/prisma/migrations/20221019142757_modules/migration.sql @@ -0,0 +1,30 @@ +-- RedefineTables +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_GuildMember" ( + "userId" TEXT NOT NULL, + "guildId" TEXT NOT NULL, + "creditsEarned" INTEGER NOT NULL DEFAULT 0, + "pointsEarned" INTEGER NOT NULL DEFAULT 0, + CONSTRAINT "GuildMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "GuildMember_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_GuildMember" ("creditsEarned", "guildId", "pointsEarned", "userId") +SELECT + coalesce("creditsEarned", 0) AS "creditsEarned", + "guildId", + coalesce("pointsEarned", 0) AS "pointsEarned", + "userId" +FROM + "GuildMember"; + +DROP TABLE "GuildMember"; + +ALTER TABLE "new_GuildMember" RENAME TO "GuildMember"; + +CREATE UNIQUE INDEX "GuildMember_userId_guildId_key" ON "GuildMember" ("userId", "guildId"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221019153247_cooldowns/migration.sql b/prisma/migrations/20221019153247_cooldowns/migration.sql new file mode 100644 index 0000000..b85f815 --- /dev/null +++ b/prisma/migrations/20221019153247_cooldowns/migration.sql @@ -0,0 +1,13 @@ +-- CreateTable +CREATE TABLE "Cooldown" ( + "guildId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "cooldown" INTEGER NOT NULL, + "timeoutId" TEXT NOT NULL, + CONSTRAINT "Cooldown_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "Cooldown_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateIndex + +CREATE UNIQUE INDEX "Cooldown_guildId_userId_timeoutId_key" ON "Cooldown" ("guildId", "userId", "timeoutId"); diff --git a/prisma/migrations/20221019153652_audit_dates/migration.sql b/prisma/migrations/20221019153652_audit_dates/migration.sql new file mode 100644 index 0000000..fd40071 --- /dev/null +++ b/prisma/migrations/20221019153652_audit_dates/migration.sql @@ -0,0 +1,155 @@ +/* + Warnings: + + - Added the required column `updatedAt` to the `Guild` table without a default value. This is not possible if the table is not empty. + - Added the required column `updatedAt` to the `Cooldown` table without a default value. This is not possible if the table is not empty. + - Added the required column `updatedAt` to the `GuildCounter` table without a default value. This is not possible if the table is not empty. + - Added the required column `updatedAt` to the `User` table without a default value. This is not possible if the table is not empty. + - Added the required column `updatedAt` to the `GuildMember` table without a default value. This is not possible if the table is not empty. + */ +-- RedefineTables + +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_Guild" ( + "id" TEXT NOT NULL, + "creditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "creditsRate" INTEGER NOT NULL DEFAULT 1, + "creditsTimeout" INTEGER NOT NULL DEFAULT 5, + "creditsWorkRate" INTEGER NOT NULL DEFAULT 25, + "creditsWorkTimeout" INTEGER NOT NULL DEFAULT 86400, + "creditsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "pointsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "pointsRate" INTEGER NOT NULL DEFAULT 1, + "pointsTimeout" INTEGER NOT NULL DEFAULT 5, + "pointsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "reputationsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "countersEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +INSERT INTO "new_Guild" ("countersEnabled", "creditsEnabled", "creditsMinimumLength", "creditsRate", "creditsTimeout", "creditsWorkRate", "creditsWorkTimeout", "id", "pointsEnabled", "pointsMinimumLength", "pointsRate", "pointsTimeout", "reputationsEnabled") +SELECT + "countersEnabled", + "creditsEnabled", + "creditsMinimumLength", + "creditsRate", + "creditsTimeout", + "creditsWorkRate", + "creditsWorkTimeout", + "id", + "pointsEnabled", + "pointsMinimumLength", + "pointsRate", + "pointsTimeout", + "reputationsEnabled" +FROM + "Guild"; + +DROP TABLE "Guild"; + +ALTER TABLE "new_Guild" RENAME TO "Guild"; + +CREATE UNIQUE INDEX "Guild_id_key" ON "Guild" ("id"); + +CREATE TABLE "new_Cooldown" ( + "guildId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "cooldown" INTEGER NOT NULL, + "timeoutId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Cooldown_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "Cooldown_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_Cooldown" ("cooldown", "guildId", "timeoutId", "userId") +SELECT + "cooldown", + "guildId", + "timeoutId", + "userId" +FROM + "Cooldown"; + +DROP TABLE "Cooldown"; + +ALTER TABLE "new_Cooldown" RENAME TO "Cooldown"; + +CREATE UNIQUE INDEX "Cooldown_guildId_userId_timeoutId_key" ON "Cooldown" ("guildId", "userId", "timeoutId"); + +CREATE TABLE "new_GuildCounter" ( + "guildId" TEXT NOT NULL, + "channelId" TEXT NOT NULL, + "triggerWord" TEXT NOT NULL, + "count" INTEGER NOT NULL DEFAULT 0, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "GuildCounter_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_GuildCounter" ("channelId", "count", "guildId", "triggerWord") +SELECT + "channelId", + "count", + "guildId", + "triggerWord" +FROM + "GuildCounter"; + +DROP TABLE "GuildCounter"; + +ALTER TABLE "new_GuildCounter" RENAME TO "GuildCounter"; + +CREATE UNIQUE INDEX "GuildCounter_guildId_channelId_key" ON "GuildCounter" ("guildId", "channelId"); + +CREATE TABLE "new_User" ( + "id" TEXT NOT NULL, + "reputationsEarned" INTEGER NOT NULL DEFAULT 0, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +INSERT INTO "new_User" ("id", "reputationsEarned") +SELECT + "id", + "reputationsEarned" +FROM + "User"; + +DROP TABLE "User"; + +ALTER TABLE "new_User" RENAME TO "User"; + +CREATE UNIQUE INDEX "User_id_key" ON "User" ("id"); + +CREATE TABLE "new_GuildMember" ( + "userId" TEXT NOT NULL, + "guildId" TEXT NOT NULL, + "creditsEarned" INTEGER NOT NULL DEFAULT 0, + "pointsEarned" INTEGER NOT NULL DEFAULT 0, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "GuildMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "GuildMember_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_GuildMember" ("creditsEarned", "guildId", "pointsEarned", "userId") +SELECT + "creditsEarned", + "guildId", + "pointsEarned", + "userId" +FROM + "GuildMember"; + +DROP TABLE "GuildMember"; + +ALTER TABLE "new_GuildMember" RENAME TO "GuildMember"; + +CREATE UNIQUE INDEX "GuildMember_userId_guildId_key" ON "GuildMember" ("userId", "guildId"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221019172846_embedconfig/migration.sql b/prisma/migrations/20221019172846_embedconfig/migration.sql new file mode 100644 index 0000000..f404f36 --- /dev/null +++ b/prisma/migrations/20221019172846_embedconfig/migration.sql @@ -0,0 +1,55 @@ +-- RedefineTables +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_Guild" ( + "id" TEXT NOT NULL, + "embedColorSuccess" TEXT NOT NULL DEFAULT '#22bb33', + "embedColorWait" TEXT NOT NULL DEFAULT '#f0ad4e', + "embedColorError" TEXT NOT NULL DEFAULT '#bb2124', + "embedFooterText" TEXT NOT NULL DEFAULT 'https://github.com/ZynerOrg/xyter', + "embedFooterIcon" TEXT NOT NULL DEFAULT 'https://github.com/ZynerOrg.png', + "creditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "creditsRate" INTEGER NOT NULL DEFAULT 1, + "creditsTimeout" INTEGER NOT NULL DEFAULT 5, + "creditsWorkRate" INTEGER NOT NULL DEFAULT 25, + "creditsWorkTimeout" INTEGER NOT NULL DEFAULT 86400, + "creditsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "pointsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "pointsRate" INTEGER NOT NULL DEFAULT 1, + "pointsTimeout" INTEGER NOT NULL DEFAULT 5, + "pointsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "reputationsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "countersEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +INSERT INTO "new_Guild" ("countersEnabled", "createdAt", "creditsEnabled", "creditsMinimumLength", "creditsRate", "creditsTimeout", "creditsWorkRate", "creditsWorkTimeout", "id", "pointsEnabled", "pointsMinimumLength", "pointsRate", "pointsTimeout", "reputationsEnabled", "updatedAt") +SELECT + "countersEnabled", + "createdAt", + "creditsEnabled", + "creditsMinimumLength", + "creditsRate", + "creditsTimeout", + "creditsWorkRate", + "creditsWorkTimeout", + "id", + "pointsEnabled", + "pointsMinimumLength", + "pointsRate", + "pointsTimeout", + "reputationsEnabled", + "updatedAt" +FROM + "Guild"; + +DROP TABLE "Guild"; + +ALTER TABLE "new_Guild" RENAME TO "Guild"; + +CREATE UNIQUE INDEX "Guild_id_key" ON "Guild" ("id"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221020081238_cpgg_api/migration.sql b/prisma/migrations/20221020081238_cpgg_api/migration.sql new file mode 100644 index 0000000..6b056d0 --- /dev/null +++ b/prisma/migrations/20221020081238_cpgg_api/migration.sql @@ -0,0 +1,12 @@ +-- AlterTable +ALTER TABLE "Guild" + ADD COLUMN "apiCpggTokenContent" TEXT; + +ALTER TABLE "Guild" + ADD COLUMN "apiCpggTokenIv" TEXT; + +ALTER TABLE "Guild" + ADD COLUMN "apiCpggUrlContent" TEXT; + +ALTER TABLE "Guild" + ADD COLUMN "apiCpggUrlIv" TEXT; diff --git a/prisma/migrations/20221020084750_cooldown_string/migration.sql b/prisma/migrations/20221020084750_cooldown_string/migration.sql new file mode 100644 index 0000000..a9521a5 --- /dev/null +++ b/prisma/migrations/20221020084750_cooldown_string/migration.sql @@ -0,0 +1,34 @@ +-- RedefineTables +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_Cooldown" ( + "guildId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "cooldown" TEXT NOT NULL, + "timeoutId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Cooldown_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "Cooldown_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_Cooldown" ("cooldown", "createdAt", "guildId", "timeoutId", "updatedAt", "userId") +SELECT + "cooldown", + "createdAt", + "guildId", + "timeoutId", + "updatedAt", + "userId" +FROM + "Cooldown"; + +DROP TABLE "Cooldown"; + +ALTER TABLE "new_Cooldown" RENAME TO "Cooldown"; + +CREATE UNIQUE INDEX "Cooldown_guildId_userId_timeoutId_key" ON "Cooldown" ("guildId", "userId", "timeoutId"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221020084922_cooldown_int/migration.sql b/prisma/migrations/20221020084922_cooldown_int/migration.sql new file mode 100644 index 0000000..822d410 --- /dev/null +++ b/prisma/migrations/20221020084922_cooldown_int/migration.sql @@ -0,0 +1,40 @@ +/* + Warnings: + + - You are about to alter the column `cooldown` on the `Cooldown` table. The data in that column could be lost. The data in that column will be cast from `String` to `Int`. + */ +-- RedefineTables + +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_Cooldown" ( + "guildId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "cooldown" INTEGER NOT NULL, + "timeoutId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Cooldown_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "Cooldown_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_Cooldown" ("cooldown", "createdAt", "guildId", "timeoutId", "updatedAt", "userId") +SELECT + "cooldown", + "createdAt", + "guildId", + "timeoutId", + "updatedAt", + "userId" +FROM + "Cooldown"; + +DROP TABLE "Cooldown"; + +ALTER TABLE "new_Cooldown" RENAME TO "Cooldown"; + +CREATE UNIQUE INDEX "Cooldown_guildId_userId_timeoutId_key" ON "Cooldown" ("guildId", "userId", "timeoutId"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221020111948_audits_module/migration.sql b/prisma/migrations/20221020111948_audits_module/migration.sql new file mode 100644 index 0000000..c44d8b6 --- /dev/null +++ b/prisma/migrations/20221020111948_audits_module/migration.sql @@ -0,0 +1,70 @@ +-- RedefineTables +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_Guild" ( + "id" TEXT NOT NULL, + "embedColorSuccess" TEXT NOT NULL DEFAULT '#22bb33', + "embedColorWait" TEXT NOT NULL DEFAULT '#f0ad4e', + "embedColorError" TEXT NOT NULL DEFAULT '#bb2124', + "embedFooterText" TEXT NOT NULL DEFAULT 'https://github.com/ZynerOrg/xyter', + "embedFooterIcon" TEXT NOT NULL DEFAULT 'https://github.com/ZynerOrg.png', + "creditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "creditsRate" INTEGER NOT NULL DEFAULT 1, + "creditsTimeout" INTEGER NOT NULL DEFAULT 5, + "creditsWorkRate" INTEGER NOT NULL DEFAULT 25, + "creditsWorkTimeout" INTEGER NOT NULL DEFAULT 86400, + "creditsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "pointsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "pointsRate" INTEGER NOT NULL DEFAULT 1, + "pointsTimeout" INTEGER NOT NULL DEFAULT 5, + "pointsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "reputationsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "countersEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "apiCpggUrlIv" TEXT, + "apiCpggUrlContent" TEXT, + "apiCpggTokenIv" TEXT, + "apiCpggTokenContent" TEXT, + "auditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "auditsChannelId" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +INSERT INTO "new_Guild" ("apiCpggTokenContent", "apiCpggTokenIv", "apiCpggUrlContent", "apiCpggUrlIv", "countersEnabled", "createdAt", "creditsEnabled", "creditsMinimumLength", "creditsRate", "creditsTimeout", "creditsWorkRate", "creditsWorkTimeout", "embedColorError", "embedColorSuccess", "embedColorWait", "embedFooterIcon", "embedFooterText", "id", "pointsEnabled", "pointsMinimumLength", "pointsRate", "pointsTimeout", "reputationsEnabled", "updatedAt") +SELECT + "apiCpggTokenContent", + "apiCpggTokenIv", + "apiCpggUrlContent", + "apiCpggUrlIv", + "countersEnabled", + "createdAt", + "creditsEnabled", + "creditsMinimumLength", + "creditsRate", + "creditsTimeout", + "creditsWorkRate", + "creditsWorkTimeout", + "embedColorError", + "embedColorSuccess", + "embedColorWait", + "embedFooterIcon", + "embedFooterText", + "id", + "pointsEnabled", + "pointsMinimumLength", + "pointsRate", + "pointsTimeout", + "reputationsEnabled", + "updatedAt" +FROM + "Guild"; + +DROP TABLE "Guild"; + +ALTER TABLE "new_Guild" RENAME TO "Guild"; + +CREATE UNIQUE INDEX "Guild_id_key" ON "Guild" ("id"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221020113312_shop_module/migration.sql b/prisma/migrations/20221020113312_shop_module/migration.sql new file mode 100644 index 0000000..8cdbd73 --- /dev/null +++ b/prisma/migrations/20221020113312_shop_module/migration.sql @@ -0,0 +1,74 @@ +-- RedefineTables +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_Guild" ( + "id" TEXT NOT NULL, + "embedColorSuccess" TEXT NOT NULL DEFAULT '#22bb33', + "embedColorWait" TEXT NOT NULL DEFAULT '#f0ad4e', + "embedColorError" TEXT NOT NULL DEFAULT '#bb2124', + "embedFooterText" TEXT NOT NULL DEFAULT 'https://github.com/ZynerOrg/xyter', + "embedFooterIcon" TEXT NOT NULL DEFAULT 'https://github.com/ZynerOrg.png', + "creditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "creditsRate" INTEGER NOT NULL DEFAULT 1, + "creditsTimeout" INTEGER NOT NULL DEFAULT 5, + "creditsWorkRate" INTEGER NOT NULL DEFAULT 25, + "creditsWorkTimeout" INTEGER NOT NULL DEFAULT 86400, + "creditsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "pointsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "pointsRate" INTEGER NOT NULL DEFAULT 1, + "pointsTimeout" INTEGER NOT NULL DEFAULT 5, + "pointsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "reputationsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "countersEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "apiCpggUrlIv" TEXT, + "apiCpggUrlContent" TEXT, + "apiCpggTokenIv" TEXT, + "apiCpggTokenContent" TEXT, + "auditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "auditsChannelId" TEXT, + "shopRolesEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "shopRolesPricePerHour" INTEGER NOT NULL DEFAULT 5, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +INSERT INTO "new_Guild" ("apiCpggTokenContent", "apiCpggTokenIv", "apiCpggUrlContent", "apiCpggUrlIv", "auditsChannelId", "auditsEnabled", "countersEnabled", "createdAt", "creditsEnabled", "creditsMinimumLength", "creditsRate", "creditsTimeout", "creditsWorkRate", "creditsWorkTimeout", "embedColorError", "embedColorSuccess", "embedColorWait", "embedFooterIcon", "embedFooterText", "id", "pointsEnabled", "pointsMinimumLength", "pointsRate", "pointsTimeout", "reputationsEnabled", "updatedAt") +SELECT + "apiCpggTokenContent", + "apiCpggTokenIv", + "apiCpggUrlContent", + "apiCpggUrlIv", + "auditsChannelId", + "auditsEnabled", + "countersEnabled", + "createdAt", + "creditsEnabled", + "creditsMinimumLength", + "creditsRate", + "creditsTimeout", + "creditsWorkRate", + "creditsWorkTimeout", + "embedColorError", + "embedColorSuccess", + "embedColorWait", + "embedFooterIcon", + "embedFooterText", + "id", + "pointsEnabled", + "pointsMinimumLength", + "pointsRate", + "pointsTimeout", + "reputationsEnabled", + "updatedAt" +FROM + "Guild"; + +DROP TABLE "Guild"; + +ALTER TABLE "new_Guild" RENAME TO "Guild"; + +CREATE UNIQUE INDEX "Guild_id_key" ON "Guild" ("id"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221020113704_welcome_module/migration.sql b/prisma/migrations/20221020113704_welcome_module/migration.sql new file mode 100644 index 0000000..7d5c6c5 --- /dev/null +++ b/prisma/migrations/20221020113704_welcome_module/migration.sql @@ -0,0 +1,79 @@ +-- RedefineTables +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_Guild" ( + "id" TEXT NOT NULL, + "embedColorSuccess" TEXT NOT NULL DEFAULT '#22bb33', + "embedColorWait" TEXT NOT NULL DEFAULT '#f0ad4e', + "embedColorError" TEXT NOT NULL DEFAULT '#bb2124', + "embedFooterText" TEXT NOT NULL DEFAULT 'https://github.com/ZynerOrg/xyter', + "embedFooterIcon" TEXT NOT NULL DEFAULT 'https://github.com/ZynerOrg.png', + "creditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "creditsRate" INTEGER NOT NULL DEFAULT 1, + "creditsTimeout" INTEGER NOT NULL DEFAULT 5, + "creditsWorkRate" INTEGER NOT NULL DEFAULT 25, + "creditsWorkTimeout" INTEGER NOT NULL DEFAULT 86400, + "creditsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "pointsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "pointsRate" INTEGER NOT NULL DEFAULT 1, + "pointsTimeout" INTEGER NOT NULL DEFAULT 5, + "pointsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "reputationsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "countersEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "apiCpggUrlIv" TEXT, + "apiCpggUrlContent" TEXT, + "apiCpggTokenIv" TEXT, + "apiCpggTokenContent" TEXT, + "auditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "auditsChannelId" TEXT, + "shopRolesEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "shopRolesPricePerHour" INTEGER NOT NULL DEFAULT 5, + "welcomeEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "welcomeJoinChannelId" TEXT, + "welcomejoinChannelMessahe" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +INSERT INTO "new_Guild" ("apiCpggTokenContent", "apiCpggTokenIv", "apiCpggUrlContent", "apiCpggUrlIv", "auditsChannelId", "auditsEnabled", "countersEnabled", "createdAt", "creditsEnabled", "creditsMinimumLength", "creditsRate", "creditsTimeout", "creditsWorkRate", "creditsWorkTimeout", "embedColorError", "embedColorSuccess", "embedColorWait", "embedFooterIcon", "embedFooterText", "id", "pointsEnabled", "pointsMinimumLength", "pointsRate", "pointsTimeout", "reputationsEnabled", "shopRolesEnabled", "shopRolesPricePerHour", "updatedAt") +SELECT + "apiCpggTokenContent", + "apiCpggTokenIv", + "apiCpggUrlContent", + "apiCpggUrlIv", + "auditsChannelId", + "auditsEnabled", + "countersEnabled", + "createdAt", + "creditsEnabled", + "creditsMinimumLength", + "creditsRate", + "creditsTimeout", + "creditsWorkRate", + "creditsWorkTimeout", + "embedColorError", + "embedColorSuccess", + "embedColorWait", + "embedFooterIcon", + "embedFooterText", + "id", + "pointsEnabled", + "pointsMinimumLength", + "pointsRate", + "pointsTimeout", + "reputationsEnabled", + "shopRolesEnabled", + "shopRolesPricePerHour", + "updatedAt" +FROM + "Guild"; + +DROP TABLE "Guild"; + +ALTER TABLE "new_Guild" RENAME TO "Guild"; + +CREATE UNIQUE INDEX "Guild_id_key" ON "Guild" ("id"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221020113756_welcome_module_correction/migration.sql b/prisma/migrations/20221020113756_welcome_module_correction/migration.sql new file mode 100644 index 0000000..1035421 --- /dev/null +++ b/prisma/migrations/20221020113756_welcome_module_correction/migration.sql @@ -0,0 +1,89 @@ +/* + Warnings: + + - You are about to drop the column `welcomejoinChannelMessahe` on the `Guild` table. All the data in the column will be lost. + */ +-- RedefineTables + +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_Guild" ( + "id" TEXT NOT NULL, + "embedColorSuccess" TEXT NOT NULL DEFAULT '#22bb33', + "embedColorWait" TEXT NOT NULL DEFAULT '#f0ad4e', + "embedColorError" TEXT NOT NULL DEFAULT '#bb2124', + "embedFooterText" TEXT NOT NULL DEFAULT 'https://github.com/ZynerOrg/xyter', + "embedFooterIcon" TEXT NOT NULL DEFAULT 'https://github.com/ZynerOrg.png', + "creditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "creditsRate" INTEGER NOT NULL DEFAULT 1, + "creditsTimeout" INTEGER NOT NULL DEFAULT 5, + "creditsWorkRate" INTEGER NOT NULL DEFAULT 25, + "creditsWorkTimeout" INTEGER NOT NULL DEFAULT 86400, + "creditsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "pointsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "pointsRate" INTEGER NOT NULL DEFAULT 1, + "pointsTimeout" INTEGER NOT NULL DEFAULT 5, + "pointsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "reputationsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "countersEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "apiCpggUrlIv" TEXT, + "apiCpggUrlContent" TEXT, + "apiCpggTokenIv" TEXT, + "apiCpggTokenContent" TEXT, + "auditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "auditsChannelId" TEXT, + "shopRolesEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "shopRolesPricePerHour" INTEGER NOT NULL DEFAULT 5, + "welcomeEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "welcomeJoinChannelId" TEXT, + "welcomeJoinChannelMessahe" TEXT, + "welcomeLeaveChannelId" TEXT, + "welcomeLeaveChannelMessage" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +INSERT INTO "new_Guild" ("apiCpggTokenContent", "apiCpggTokenIv", "apiCpggUrlContent", "apiCpggUrlIv", "auditsChannelId", "auditsEnabled", "countersEnabled", "createdAt", "creditsEnabled", "creditsMinimumLength", "creditsRate", "creditsTimeout", "creditsWorkRate", "creditsWorkTimeout", "embedColorError", "embedColorSuccess", "embedColorWait", "embedFooterIcon", "embedFooterText", "id", "pointsEnabled", "pointsMinimumLength", "pointsRate", "pointsTimeout", "reputationsEnabled", "shopRolesEnabled", "shopRolesPricePerHour", "updatedAt", "welcomeEnabled", "welcomeJoinChannelId") +SELECT + "apiCpggTokenContent", + "apiCpggTokenIv", + "apiCpggUrlContent", + "apiCpggUrlIv", + "auditsChannelId", + "auditsEnabled", + "countersEnabled", + "createdAt", + "creditsEnabled", + "creditsMinimumLength", + "creditsRate", + "creditsTimeout", + "creditsWorkRate", + "creditsWorkTimeout", + "embedColorError", + "embedColorSuccess", + "embedColorWait", + "embedFooterIcon", + "embedFooterText", + "id", + "pointsEnabled", + "pointsMinimumLength", + "pointsRate", + "pointsTimeout", + "reputationsEnabled", + "shopRolesEnabled", + "shopRolesPricePerHour", + "updatedAt", + "welcomeEnabled", + "welcomeJoinChannelId" +FROM + "Guild"; + +DROP TABLE "Guild"; + +ALTER TABLE "new_Guild" RENAME TO "Guild"; + +CREATE UNIQUE INDEX "Guild_id_key" ON "Guild" ("id"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221020113831_welcome_module_correction/migration.sql b/prisma/migrations/20221020113831_welcome_module_correction/migration.sql new file mode 100644 index 0000000..0c05518 --- /dev/null +++ b/prisma/migrations/20221020113831_welcome_module_correction/migration.sql @@ -0,0 +1,91 @@ +/* + Warnings: + + - You are about to drop the column `welcomeJoinChannelMessahe` on the `Guild` table. All the data in the column will be lost. + */ +-- RedefineTables + +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_Guild" ( + "id" TEXT NOT NULL, + "embedColorSuccess" TEXT NOT NULL DEFAULT '#22bb33', + "embedColorWait" TEXT NOT NULL DEFAULT '#f0ad4e', + "embedColorError" TEXT NOT NULL DEFAULT '#bb2124', + "embedFooterText" TEXT NOT NULL DEFAULT 'https://github.com/ZynerOrg/xyter', + "embedFooterIcon" TEXT NOT NULL DEFAULT 'https://github.com/ZynerOrg.png', + "creditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "creditsRate" INTEGER NOT NULL DEFAULT 1, + "creditsTimeout" INTEGER NOT NULL DEFAULT 5, + "creditsWorkRate" INTEGER NOT NULL DEFAULT 25, + "creditsWorkTimeout" INTEGER NOT NULL DEFAULT 86400, + "creditsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "pointsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "pointsRate" INTEGER NOT NULL DEFAULT 1, + "pointsTimeout" INTEGER NOT NULL DEFAULT 5, + "pointsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "reputationsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "countersEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "apiCpggUrlIv" TEXT, + "apiCpggUrlContent" TEXT, + "apiCpggTokenIv" TEXT, + "apiCpggTokenContent" TEXT, + "auditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "auditsChannelId" TEXT, + "shopRolesEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "shopRolesPricePerHour" INTEGER NOT NULL DEFAULT 5, + "welcomeEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "welcomeJoinChannelId" TEXT, + "welcomeJoinChannelMessage" TEXT, + "welcomeLeaveChannelId" TEXT, + "welcomeLeaveChannelMessage" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +INSERT INTO "new_Guild" ("apiCpggTokenContent", "apiCpggTokenIv", "apiCpggUrlContent", "apiCpggUrlIv", "auditsChannelId", "auditsEnabled", "countersEnabled", "createdAt", "creditsEnabled", "creditsMinimumLength", "creditsRate", "creditsTimeout", "creditsWorkRate", "creditsWorkTimeout", "embedColorError", "embedColorSuccess", "embedColorWait", "embedFooterIcon", "embedFooterText", "id", "pointsEnabled", "pointsMinimumLength", "pointsRate", "pointsTimeout", "reputationsEnabled", "shopRolesEnabled", "shopRolesPricePerHour", "updatedAt", "welcomeEnabled", "welcomeJoinChannelId", "welcomeLeaveChannelId", "welcomeLeaveChannelMessage") +SELECT + "apiCpggTokenContent", + "apiCpggTokenIv", + "apiCpggUrlContent", + "apiCpggUrlIv", + "auditsChannelId", + "auditsEnabled", + "countersEnabled", + "createdAt", + "creditsEnabled", + "creditsMinimumLength", + "creditsRate", + "creditsTimeout", + "creditsWorkRate", + "creditsWorkTimeout", + "embedColorError", + "embedColorSuccess", + "embedColorWait", + "embedFooterIcon", + "embedFooterText", + "id", + "pointsEnabled", + "pointsMinimumLength", + "pointsRate", + "pointsTimeout", + "reputationsEnabled", + "shopRolesEnabled", + "shopRolesPricePerHour", + "updatedAt", + "welcomeEnabled", + "welcomeJoinChannelId", + "welcomeLeaveChannelId", + "welcomeLeaveChannelMessage" +FROM + "Guild"; + +DROP TABLE "Guild"; + +ALTER TABLE "new_Guild" RENAME TO "Guild"; + +CREATE UNIQUE INDEX "Guild_id_key" ON "Guild" ("id"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221021150615_shoproles_model/migration.sql b/prisma/migrations/20221021150615_shoproles_model/migration.sql new file mode 100644 index 0000000..3d9bab2 --- /dev/null +++ b/prisma/migrations/20221021150615_shoproles_model/migration.sql @@ -0,0 +1,17 @@ +-- CreateTable +CREATE TABLE "GuildShopRoles" ( + "guildId" TEXT NOT NULL, + "channelId" TEXT NOT NULL, + "roleId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "pricePerHour" INTEGER NOT NULL DEFAULT 5, + "lastPayed" DATETIME NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "GuildShopRoles_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "GuildShopRoles_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateIndex + +CREATE UNIQUE INDEX "GuildShopRoles_guildId_channelId_key" ON "GuildShopRoles" ("guildId", "channelId"); diff --git a/prisma/migrations/20221021151136_shoproles_model_correction/migration.sql b/prisma/migrations/20221021151136_shoproles_model_correction/migration.sql new file mode 100644 index 0000000..f3a4e3a --- /dev/null +++ b/prisma/migrations/20221021151136_shoproles_model_correction/migration.sql @@ -0,0 +1,42 @@ +/* + Warnings: + + - You are about to drop the column `channelId` on the `GuildShopRoles` table. All the data in the column will be lost. + */ +-- RedefineTables + +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_GuildShopRoles" ( + "guildId" TEXT NOT NULL, + "roleId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "pricePerHour" INTEGER NOT NULL DEFAULT 5, + "lastPayed" DATETIME NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "GuildShopRoles_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "GuildShopRoles_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_GuildShopRoles" ("createdAt", "guildId", "lastPayed", "pricePerHour", "roleId", "updatedAt", "userId") +SELECT + "createdAt", + "guildId", + "lastPayed", + "pricePerHour", + "roleId", + "updatedAt", + "userId" +FROM + "GuildShopRoles"; + +DROP TABLE "GuildShopRoles"; + +ALTER TABLE "new_GuildShopRoles" RENAME TO "GuildShopRoles"; + +CREATE UNIQUE INDEX "GuildShopRoles_guildId_userId_roleId_key" ON "GuildShopRoles" ("guildId", "userId", "roleId"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221021151337_shoproles_model_correction/migration.sql b/prisma/migrations/20221021151337_shoproles_model_correction/migration.sql new file mode 100644 index 0000000..52dc513 --- /dev/null +++ b/prisma/migrations/20221021151337_shoproles_model_correction/migration.sql @@ -0,0 +1,39 @@ +-- RedefineTables +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_GuildShopRoles" ( + "guildId" TEXT NOT NULL, + "roleId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "pricePerHour" INTEGER NOT NULL DEFAULT 5, + "lastPayed" DATETIME NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "GuildShopRoles_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "GuildShopRoles_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "GuildShopRoles_guildId_userId_fkey" FOREIGN KEY ("guildId", + "userId") REFERENCES "GuildMember" ("guildId", + "userId") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_GuildShopRoles" ("createdAt", "guildId", "lastPayed", "pricePerHour", "roleId", "updatedAt", "userId") +SELECT + "createdAt", + "guildId", + "lastPayed", + "pricePerHour", + "roleId", + "updatedAt", + "userId" +FROM + "GuildShopRoles"; + +DROP TABLE "GuildShopRoles"; + +ALTER TABLE "new_GuildShopRoles" RENAME TO "GuildShopRoles"; + +CREATE UNIQUE INDEX "GuildShopRoles_guildId_userId_roleId_key" ON "GuildShopRoles" ("guildId", "userId", "roleId"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221021153546_shoproles_model_correction/migration.sql b/prisma/migrations/20221021153546_shoproles_model_correction/migration.sql new file mode 100644 index 0000000..555cf58 --- /dev/null +++ b/prisma/migrations/20221021153546_shoproles_model_correction/migration.sql @@ -0,0 +1,39 @@ +-- RedefineTables +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_GuildShopRoles" ( + "guildId" TEXT NOT NULL, + "roleId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "pricePerHour" INTEGER NOT NULL DEFAULT 5, + "lastPayed" DATETIME NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "GuildShopRoles_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "GuildShopRoles_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "GuildShopRoles_userId_guildId_fkey" FOREIGN KEY ("userId", + "guildId") REFERENCES "GuildMember" ("userId", + "guildId") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_GuildShopRoles" ("createdAt", "guildId", "lastPayed", "pricePerHour", "roleId", "updatedAt", "userId") +SELECT + "createdAt", + "guildId", + "lastPayed", + "pricePerHour", + "roleId", + "updatedAt", + "userId" +FROM + "GuildShopRoles"; + +DROP TABLE "GuildShopRoles"; + +ALTER TABLE "new_GuildShopRoles" RENAME TO "GuildShopRoles"; + +CREATE UNIQUE INDEX "GuildShopRoles_guildId_userId_roleId_key" ON "GuildShopRoles" ("guildId", "userId", "roleId"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221021172156_int_to_bigint/migration.sql b/prisma/migrations/20221021172156_int_to_bigint/migration.sql new file mode 100644 index 0000000..8ba2555 --- /dev/null +++ b/prisma/migrations/20221021172156_int_to_bigint/migration.sql @@ -0,0 +1,244 @@ +/* + Warnings: + + - You are about to alter the column `count` on the `GuildCounter` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`. + - You are about to alter the column `creditsEarned` on the `GuildMember` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`. + - You are about to alter the column `pointsEarned` on the `GuildMember` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`. + - You are about to alter the column `pricePerHour` on the `GuildShopRoles` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`. + - You are about to alter the column `cooldown` on the `Cooldown` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`. + - You are about to alter the column `creditsMinimumLength` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`. + - You are about to alter the column `creditsRate` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`. + - You are about to alter the column `creditsTimeout` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`. + - You are about to alter the column `creditsWorkRate` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`. + - You are about to alter the column `creditsWorkTimeout` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`. + - You are about to alter the column `pointsMinimumLength` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`. + - You are about to alter the column `pointsRate` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`. + - You are about to alter the column `pointsTimeout` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`. + - You are about to alter the column `shopRolesPricePerHour` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`. + - You are about to alter the column `reputationsEarned` on the `User` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`. + */ +-- RedefineTables + +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_GuildCounter" ( + "guildId" TEXT NOT NULL, + "channelId" TEXT NOT NULL, + "triggerWord" TEXT NOT NULL, + "count" BIGINT NOT NULL DEFAULT 0, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "GuildCounter_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_GuildCounter" ("channelId", "count", "createdAt", "guildId", "triggerWord", "updatedAt") +SELECT + "channelId", + "count", + "createdAt", + "guildId", + "triggerWord", + "updatedAt" +FROM + "GuildCounter"; + +DROP TABLE "GuildCounter"; + +ALTER TABLE "new_GuildCounter" RENAME TO "GuildCounter"; + +CREATE UNIQUE INDEX "GuildCounter_guildId_channelId_key" ON "GuildCounter" ("guildId", "channelId"); + +CREATE TABLE "new_GuildMember" ( + "userId" TEXT NOT NULL, + "guildId" TEXT NOT NULL, + "creditsEarned" BIGINT NOT NULL DEFAULT 0, + "pointsEarned" BIGINT NOT NULL DEFAULT 0, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "GuildMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "GuildMember_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_GuildMember" ("createdAt", "creditsEarned", "guildId", "pointsEarned", "updatedAt", "userId") +SELECT + "createdAt", + "creditsEarned", + "guildId", + "pointsEarned", + "updatedAt", + "userId" +FROM + "GuildMember"; + +DROP TABLE "GuildMember"; + +ALTER TABLE "new_GuildMember" RENAME TO "GuildMember"; + +CREATE UNIQUE INDEX "GuildMember_userId_guildId_key" ON "GuildMember" ("userId", "guildId"); + +CREATE TABLE "new_GuildShopRoles" ( + "guildId" TEXT NOT NULL, + "roleId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "pricePerHour" BIGINT NOT NULL DEFAULT 5, + "lastPayed" DATETIME NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "GuildShopRoles_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "GuildShopRoles_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "GuildShopRoles_userId_guildId_fkey" FOREIGN KEY ("userId", + "guildId") REFERENCES "GuildMember" ("userId", + "guildId") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_GuildShopRoles" ("createdAt", "guildId", "lastPayed", "pricePerHour", "roleId", "updatedAt", "userId") +SELECT + "createdAt", + "guildId", + "lastPayed", + "pricePerHour", + "roleId", + "updatedAt", + "userId" +FROM + "GuildShopRoles"; + +DROP TABLE "GuildShopRoles"; + +ALTER TABLE "new_GuildShopRoles" RENAME TO "GuildShopRoles"; + +CREATE UNIQUE INDEX "GuildShopRoles_guildId_userId_roleId_key" ON "GuildShopRoles" ("guildId", "userId", "roleId"); + +CREATE TABLE "new_Cooldown" ( + "guildId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "cooldown" BIGINT NOT NULL, + "timeoutId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Cooldown_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "Cooldown_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_Cooldown" ("cooldown", "createdAt", "guildId", "timeoutId", "updatedAt", "userId") +SELECT + "cooldown", + "createdAt", + "guildId", + "timeoutId", + "updatedAt", + "userId" +FROM + "Cooldown"; + +DROP TABLE "Cooldown"; + +ALTER TABLE "new_Cooldown" RENAME TO "Cooldown"; + +CREATE UNIQUE INDEX "Cooldown_guildId_userId_timeoutId_key" ON "Cooldown" ("guildId", "userId", "timeoutId"); + +CREATE TABLE "new_Guild" ( + "id" TEXT NOT NULL, + "embedColorSuccess" TEXT NOT NULL DEFAULT '#22bb33', + "embedColorWait" TEXT NOT NULL DEFAULT '#f0ad4e', + "embedColorError" TEXT NOT NULL DEFAULT '#bb2124', + "embedFooterText" TEXT NOT NULL DEFAULT 'https://github.com/ZynerOrg/xyter', + "embedFooterIcon" TEXT NOT NULL DEFAULT 'https://github.com/ZynerOrg.png', + "creditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "creditsRate" BIGINT NOT NULL DEFAULT 1, + "creditsTimeout" BIGINT NOT NULL DEFAULT 5, + "creditsWorkRate" BIGINT NOT NULL DEFAULT 25, + "creditsWorkTimeout" BIGINT NOT NULL DEFAULT 86400, + "creditsMinimumLength" BIGINT NOT NULL DEFAULT 5, + "pointsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "pointsRate" BIGINT NOT NULL DEFAULT 1, + "pointsTimeout" BIGINT NOT NULL DEFAULT 5, + "pointsMinimumLength" BIGINT NOT NULL DEFAULT 5, + "reputationsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "countersEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "apiCpggUrlIv" TEXT, + "apiCpggUrlContent" TEXT, + "apiCpggTokenIv" TEXT, + "apiCpggTokenContent" TEXT, + "auditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "auditsChannelId" TEXT, + "shopRolesEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "shopRolesPricePerHour" BIGINT NOT NULL DEFAULT 5, + "welcomeEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "welcomeJoinChannelId" TEXT, + "welcomeJoinChannelMessage" TEXT, + "welcomeLeaveChannelId" TEXT, + "welcomeLeaveChannelMessage" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +INSERT INTO "new_Guild" ("apiCpggTokenContent", "apiCpggTokenIv", "apiCpggUrlContent", "apiCpggUrlIv", "auditsChannelId", "auditsEnabled", "countersEnabled", "createdAt", "creditsEnabled", "creditsMinimumLength", "creditsRate", "creditsTimeout", "creditsWorkRate", "creditsWorkTimeout", "embedColorError", "embedColorSuccess", "embedColorWait", "embedFooterIcon", "embedFooterText", "id", "pointsEnabled", "pointsMinimumLength", "pointsRate", "pointsTimeout", "reputationsEnabled", "shopRolesEnabled", "shopRolesPricePerHour", "updatedAt", "welcomeEnabled", "welcomeJoinChannelId", "welcomeJoinChannelMessage", "welcomeLeaveChannelId", "welcomeLeaveChannelMessage") +SELECT + "apiCpggTokenContent", + "apiCpggTokenIv", + "apiCpggUrlContent", + "apiCpggUrlIv", + "auditsChannelId", + "auditsEnabled", + "countersEnabled", + "createdAt", + "creditsEnabled", + "creditsMinimumLength", + "creditsRate", + "creditsTimeout", + "creditsWorkRate", + "creditsWorkTimeout", + "embedColorError", + "embedColorSuccess", + "embedColorWait", + "embedFooterIcon", + "embedFooterText", + "id", + "pointsEnabled", + "pointsMinimumLength", + "pointsRate", + "pointsTimeout", + "reputationsEnabled", + "shopRolesEnabled", + "shopRolesPricePerHour", + "updatedAt", + "welcomeEnabled", + "welcomeJoinChannelId", + "welcomeJoinChannelMessage", + "welcomeLeaveChannelId", + "welcomeLeaveChannelMessage" +FROM + "Guild"; + +DROP TABLE "Guild"; + +ALTER TABLE "new_Guild" RENAME TO "Guild"; + +CREATE UNIQUE INDEX "Guild_id_key" ON "Guild" ("id"); + +CREATE TABLE "new_User" ( + "id" TEXT NOT NULL, + "reputationsEarned" BIGINT NOT NULL DEFAULT 0, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +INSERT INTO "new_User" ("createdAt", "id", "reputationsEarned", "updatedAt") +SELECT + "createdAt", + "id", + "reputationsEarned", + "updatedAt" +FROM + "User"; + +DROP TABLE "User"; + +ALTER TABLE "new_User" RENAME TO "User"; + +CREATE UNIQUE INDEX "User_id_key" ON "User" ("id"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/20221021172345_bigint_to_int/migration.sql b/prisma/migrations/20221021172345_bigint_to_int/migration.sql new file mode 100644 index 0000000..29f4f08 --- /dev/null +++ b/prisma/migrations/20221021172345_bigint_to_int/migration.sql @@ -0,0 +1,244 @@ +/* + Warnings: + + - You are about to alter the column `reputationsEarned` on the `User` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `count` on the `GuildCounter` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `creditsEarned` on the `GuildMember` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `pointsEarned` on the `GuildMember` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `pricePerHour` on the `GuildShopRoles` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `creditsMinimumLength` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `creditsRate` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `creditsTimeout` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `creditsWorkRate` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `creditsWorkTimeout` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `pointsMinimumLength` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `pointsRate` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `pointsTimeout` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `shopRolesPricePerHour` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `cooldown` on the `Cooldown` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + */ +-- RedefineTables + +PRAGMA foreign_keys = OFF; + +CREATE TABLE "new_User" ( + "id" TEXT NOT NULL, + "reputationsEarned" INTEGER NOT NULL DEFAULT 0, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +INSERT INTO "new_User" ("createdAt", "id", "reputationsEarned", "updatedAt") +SELECT + "createdAt", + "id", + "reputationsEarned", + "updatedAt" +FROM + "User"; + +DROP TABLE "User"; + +ALTER TABLE "new_User" RENAME TO "User"; + +CREATE UNIQUE INDEX "User_id_key" ON "User" ("id"); + +CREATE TABLE "new_GuildCounter" ( + "guildId" TEXT NOT NULL, + "channelId" TEXT NOT NULL, + "triggerWord" TEXT NOT NULL, + "count" INTEGER NOT NULL DEFAULT 0, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "GuildCounter_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_GuildCounter" ("channelId", "count", "createdAt", "guildId", "triggerWord", "updatedAt") +SELECT + "channelId", + "count", + "createdAt", + "guildId", + "triggerWord", + "updatedAt" +FROM + "GuildCounter"; + +DROP TABLE "GuildCounter"; + +ALTER TABLE "new_GuildCounter" RENAME TO "GuildCounter"; + +CREATE UNIQUE INDEX "GuildCounter_guildId_channelId_key" ON "GuildCounter" ("guildId", "channelId"); + +CREATE TABLE "new_GuildMember" ( + "userId" TEXT NOT NULL, + "guildId" TEXT NOT NULL, + "creditsEarned" INTEGER NOT NULL DEFAULT 0, + "pointsEarned" INTEGER NOT NULL DEFAULT 0, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "GuildMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "GuildMember_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_GuildMember" ("createdAt", "creditsEarned", "guildId", "pointsEarned", "updatedAt", "userId") +SELECT + "createdAt", + "creditsEarned", + "guildId", + "pointsEarned", + "updatedAt", + "userId" +FROM + "GuildMember"; + +DROP TABLE "GuildMember"; + +ALTER TABLE "new_GuildMember" RENAME TO "GuildMember"; + +CREATE UNIQUE INDEX "GuildMember_userId_guildId_key" ON "GuildMember" ("userId", "guildId"); + +CREATE TABLE "new_GuildShopRoles" ( + "guildId" TEXT NOT NULL, + "roleId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "pricePerHour" INTEGER NOT NULL DEFAULT 5, + "lastPayed" DATETIME NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "GuildShopRoles_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "GuildShopRoles_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "GuildShopRoles_userId_guildId_fkey" FOREIGN KEY ("userId", + "guildId") REFERENCES "GuildMember" ("userId", + "guildId") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_GuildShopRoles" ("createdAt", "guildId", "lastPayed", "pricePerHour", "roleId", "updatedAt", "userId") +SELECT + "createdAt", + "guildId", + "lastPayed", + "pricePerHour", + "roleId", + "updatedAt", + "userId" +FROM + "GuildShopRoles"; + +DROP TABLE "GuildShopRoles"; + +ALTER TABLE "new_GuildShopRoles" RENAME TO "GuildShopRoles"; + +CREATE UNIQUE INDEX "GuildShopRoles_guildId_userId_roleId_key" ON "GuildShopRoles" ("guildId", "userId", "roleId"); + +CREATE TABLE "new_Guild" ( + "id" TEXT NOT NULL, + "embedColorSuccess" TEXT NOT NULL DEFAULT '#22bb33', + "embedColorWait" TEXT NOT NULL DEFAULT '#f0ad4e', + "embedColorError" TEXT NOT NULL DEFAULT '#bb2124', + "embedFooterText" TEXT NOT NULL DEFAULT 'https://github.com/ZynerOrg/xyter', + "embedFooterIcon" TEXT NOT NULL DEFAULT 'https://github.com/ZynerOrg.png', + "creditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "creditsRate" INTEGER NOT NULL DEFAULT 1, + "creditsTimeout" INTEGER NOT NULL DEFAULT 5, + "creditsWorkRate" INTEGER NOT NULL DEFAULT 25, + "creditsWorkTimeout" INTEGER NOT NULL DEFAULT 86400, + "creditsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "pointsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "pointsRate" INTEGER NOT NULL DEFAULT 1, + "pointsTimeout" INTEGER NOT NULL DEFAULT 5, + "pointsMinimumLength" INTEGER NOT NULL DEFAULT 5, + "reputationsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "countersEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "apiCpggUrlIv" TEXT, + "apiCpggUrlContent" TEXT, + "apiCpggTokenIv" TEXT, + "apiCpggTokenContent" TEXT, + "auditsEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "auditsChannelId" TEXT, + "shopRolesEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "shopRolesPricePerHour" INTEGER NOT NULL DEFAULT 5, + "welcomeEnabled" BOOLEAN NOT NULL DEFAULT FALSE, + "welcomeJoinChannelId" TEXT, + "welcomeJoinChannelMessage" TEXT, + "welcomeLeaveChannelId" TEXT, + "welcomeLeaveChannelMessage" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +INSERT INTO "new_Guild" ("apiCpggTokenContent", "apiCpggTokenIv", "apiCpggUrlContent", "apiCpggUrlIv", "auditsChannelId", "auditsEnabled", "countersEnabled", "createdAt", "creditsEnabled", "creditsMinimumLength", "creditsRate", "creditsTimeout", "creditsWorkRate", "creditsWorkTimeout", "embedColorError", "embedColorSuccess", "embedColorWait", "embedFooterIcon", "embedFooterText", "id", "pointsEnabled", "pointsMinimumLength", "pointsRate", "pointsTimeout", "reputationsEnabled", "shopRolesEnabled", "shopRolesPricePerHour", "updatedAt", "welcomeEnabled", "welcomeJoinChannelId", "welcomeJoinChannelMessage", "welcomeLeaveChannelId", "welcomeLeaveChannelMessage") +SELECT + "apiCpggTokenContent", + "apiCpggTokenIv", + "apiCpggUrlContent", + "apiCpggUrlIv", + "auditsChannelId", + "auditsEnabled", + "countersEnabled", + "createdAt", + "creditsEnabled", + "creditsMinimumLength", + "creditsRate", + "creditsTimeout", + "creditsWorkRate", + "creditsWorkTimeout", + "embedColorError", + "embedColorSuccess", + "embedColorWait", + "embedFooterIcon", + "embedFooterText", + "id", + "pointsEnabled", + "pointsMinimumLength", + "pointsRate", + "pointsTimeout", + "reputationsEnabled", + "shopRolesEnabled", + "shopRolesPricePerHour", + "updatedAt", + "welcomeEnabled", + "welcomeJoinChannelId", + "welcomeJoinChannelMessage", + "welcomeLeaveChannelId", + "welcomeLeaveChannelMessage" +FROM + "Guild"; + +DROP TABLE "Guild"; + +ALTER TABLE "new_Guild" RENAME TO "Guild"; + +CREATE UNIQUE INDEX "Guild_id_key" ON "Guild" ("id"); + +CREATE TABLE "new_Cooldown" ( + "guildId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "cooldown" INTEGER NOT NULL, + "timeoutId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Cooldown_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "Cooldown_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +INSERT INTO "new_Cooldown" ("cooldown", "createdAt", "guildId", "timeoutId", "updatedAt", "userId") +SELECT + "cooldown", + "createdAt", + "guildId", + "timeoutId", + "updatedAt", + "userId" +FROM + "Cooldown"; + +DROP TABLE "Cooldown"; + +ALTER TABLE "new_Cooldown" RENAME TO "Cooldown"; + +CREATE UNIQUE INDEX "Cooldown_guildId_userId_timeoutId_key" ON "Cooldown" ("guildId", "userId", "timeoutId"); + +PRAGMA foreign_key_check; + +PRAGMA foreign_keys = ON; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..6fcf33d --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "sqlite" diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..f7923d4 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,144 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" + previewFeatures = ["interactiveTransactions"] +} + +datasource db { + provider = "sqlite" + url = env("DATABASE_URL") +} + +model Guild { + id String @unique + guildMembers GuildMember[] + cooldowns Cooldown[] + + // Settings + embedColorSuccess String @default("#22bb33") + embedColorWait String @default("#f0ad4e") + embedColorError String @default("#bb2124") + embedFooterText String @default("https://github.com/ZynerOrg/xyter") + embedFooterIcon String @default("https://github.com/ZynerOrg.png") + + // Modules + creditsEnabled Boolean @default(false) + creditsRate Int @default(1) + creditsTimeout Int @default(5) + creditsWorkRate Int @default(25) + creditsWorkTimeout Int @default(86400) + creditsMinimumLength Int @default(5) + + pointsEnabled Boolean @default(false) + pointsRate Int @default(1) + pointsTimeout Int @default(5) + pointsMinimumLength Int @default(5) + + reputationsEnabled Boolean @default(false) + + countersEnabled Boolean @default(false) + counters GuildCounter[] + + apiCpggUrlIv String? + apiCpggUrlContent String? + apiCpggTokenIv String? + apiCpggTokenContent String? + + auditsEnabled Boolean @default(false) + auditsChannelId String? + + shopRolesEnabled Boolean @default(false) + shopRolesPricePerHour Int @default(5) + + welcomeEnabled Boolean @default(false) + welcomeJoinChannelId String? + welcomeJoinChannelMessage String? + welcomeLeaveChannelId String? + welcomeLeaveChannelMessage String? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + GuildShopRoles GuildShopRoles[] +} + +model User { + id String @unique + GuildMember GuildMember[] + + // Settings + + // Modules + reputationsEarned Int @default(0) + Cooldown Cooldown[] + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + GuildShopRoles GuildShopRoles[] +} + +model GuildMember { + userId String + guildId String + + user User @relation(fields: [userId], references: [id]) + guild Guild @relation(fields: [guildId], references: [id]) + + // Settings + + // Modules + creditsEarned Int @default(0) + pointsEarned Int @default(0) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + GuildShopRoles GuildShopRoles[] + + // Unique Identifier + @@unique([userId, guildId]) +} + +model GuildCounter { + guildId String + channelId String + triggerWord String + count Int @default(0) + guild Guild @relation(fields: [guildId], references: [id]) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@unique([guildId, channelId]) +} + +model Cooldown { + guild Guild @relation(fields: [guildId], references: [id]) + user User @relation(fields: [userId], references: [id]) + guildId String + userId String + cooldown Int + timeoutId String + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@unique([guildId, userId, timeoutId]) +} + +model GuildShopRoles { + guildId String + roleId String + userId String + pricePerHour Int @default(5) + lastPayed DateTime + + guild Guild @relation(fields: [guildId], references: [id]) + user User @relation(fields: [userId], references: [id]) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + member GuildMember? @relation(fields: [userId, guildId], references: [userId, guildId]) + + @@unique([guildId, userId, roleId]) +} diff --git a/src/plugins/buttons/primary/index.ts b/src/buttons/primary/index.ts similarity index 57% rename from src/plugins/buttons/primary/index.ts rename to src/buttons/primary/index.ts index 83162a6..6e95e0e 100644 --- a/src/plugins/buttons/primary/index.ts +++ b/src/buttons/primary/index.ts @@ -1,8 +1,9 @@ import { ButtonInteraction } from "discord.js"; -import logger from "../../../logger"; +import logger from "../../middlewares/logger"; export const metadata = { guildOnly: false, ephemeral: false }; -export const execute = async (interaction: ButtonInteraction) => { +// Execute the function +export const execute = (interaction: ButtonInteraction) => { logger.debug(interaction.customId, "primary button clicked!"); }; diff --git a/src/commands/config/index.ts b/src/commands/config/index.ts new file mode 100644 index 0000000..0d2a3eb --- /dev/null +++ b/src/commands/config/index.ts @@ -0,0 +1,54 @@ +import { SlashCommandBuilder } from "@discordjs/builders"; +import { ChatInputCommandInteraction } from "discord.js"; + +// Modules +import moduleAudits from "./modules/audits"; +import moduleCpgg from "./modules/cpgg"; +import moduleCredits from "./modules/credits"; +import moduleEmbeds from "./modules/embeds"; +import modulePoints from "./modules/points"; +import moduleShop from "./modules/shop"; +import moduleWelcome from "./modules/welcome"; + +export const builder = new SlashCommandBuilder() + .setName("config") + .setDescription("Manage guild configurations.") + .setDMPermission(false) + + // Modules + .addSubcommand(moduleAudits.builder) + .addSubcommand(moduleCpgg.builder) + .addSubcommand(moduleCredits.builder) + .addSubcommand(moduleEmbeds.builder) + .addSubcommand(modulePoints.builder) + .addSubcommand(moduleShop.builder) + .addSubcommand(moduleWelcome.builder); + +// Execute function +export const execute = async (interaction: ChatInputCommandInteraction) => { + switch (interaction.options.getSubcommand()) { + case "audits": + await moduleAudits.execute(interaction); + break; + case "cpgg": + await moduleCpgg.execute(interaction); + break; + case "credits": + await moduleCredits.execute(interaction); + break; + case "embeds": + await moduleEmbeds.execute(interaction); + break; + case "points": + await modulePoints.execute(interaction); + break; + case "shop": + await moduleShop.execute(interaction); + break; + case "welcome": + await moduleWelcome.execute(interaction); + break; + default: + throw new Error("No module found for that specific command."); + } +}; diff --git a/src/commands/config/modules/audits/index.ts b/src/commands/config/modules/audits/index.ts new file mode 100644 index 0000000..bc91291 --- /dev/null +++ b/src/commands/config/modules/audits/index.ts @@ -0,0 +1,97 @@ +import { ChannelType } from "discord-api-types/v10"; +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import checkPermission from "../../../../helpers/checkPermission"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; + +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("audits") + .setDescription("Audits") + .addBooleanOption((option) => + option + .setName("status") + .setDescription("Should audits be enabled?") + .setRequired(true) + ) + .addChannelOption((option) => + option + .setName("channel") + .setDescription("Channel for audit messages.") + .addChannelTypes(ChannelType.GuildText) + .setRequired(true) + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { guild, options } = interaction; + const { successColor, footerText, footerIcon } = await getEmbedConfig( + guild + ); + const status = options.getBoolean("status"); + const channel = options.getChannel("channel"); + + if (!guild) throw new Error("Guild not found."); + if (!channel) throw new Error("Channel not found."); + if (status === null) throw new Error("Status not found."); + + const createGuild = await prisma.guild.upsert({ + where: { + id: guild.id, + }, + update: { + auditsEnabled: status, + auditsChannelId: channel.id, + }, + create: { + id: guild.id, + auditsEnabled: status, + auditsChannelId: channel.id, + }, + }); + + logger.silly(createGuild); + + const embedSuccess = new EmbedBuilder() + .setTitle("[:hammer:] Audits") + .setDescription("Guild configuration updated successfully.") + .setColor(successColor) + .addFields( + { + name: "🤖 Status", + value: `${ + createGuild.auditsEnabled + ? ":white_check_mark: Enabled" + : ":x: Disabled" + }`, + inline: true, + }, + { + name: "🌊 Channel", + value: `<#${createGuild.auditsChannelId}>`, + inline: true, + } + ) + .setTimestamp() + .setFooter({ + iconURL: footerIcon, + text: footerText, + }); + + await interaction.editReply({ + embeds: [embedSuccess], + }); + return; + }, +}; diff --git a/src/commands/config/modules/cpgg/index.ts b/src/commands/config/modules/cpgg/index.ts new file mode 100644 index 0000000..56daed8 --- /dev/null +++ b/src/commands/config/modules/cpgg/index.ts @@ -0,0 +1,106 @@ +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, +} from "discord.js"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import checkPermission from "../../../../helpers/checkPermission"; +import encryption from "../../../../helpers/encryption"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; + +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("cpgg") + .setDescription("Controlpanel.gg") + .addStringOption((option) => + option + .setName("scheme") + .setDescription(`Controlpanel.gg Scheme`) + .setRequired(true) + .setChoices( + { name: "HTTPS (secure)", value: "https" }, + { name: "HTTP (insecure)", value: "http" } + ) + ) + .addStringOption((option) => + option + .setName("domain") + .setDescription(`Controlpanel.gg Domain`) + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("token") + .setDescription(`Controlpanel.gg Application API`) + .setRequired(true) + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + const { options, guild } = interaction; + + const tokenData = options.getString("token"); + const scheme = options.getString("scheme"); + const domain = options.getString("domain"); + const token = tokenData && encryption.encrypt(tokenData); + const url = scheme && domain && encryption.encrypt(`${scheme}://${domain}`); + + if (!guild) throw new Error("No guild found"); + if (!token) throw new Error("Token not found"); + if (!url) throw new Error("URL not found"); + + const createGuild = await prisma.guild.upsert({ + where: { + id: guild.id, + }, + update: { + apiCpggTokenIv: token.iv, + apiCpggTokenContent: token.content, + apiCpggUrlIv: url.iv, + apiCpggUrlContent: url.content, + }, + create: { + id: guild.id, + apiCpggTokenIv: token.iv, + apiCpggTokenContent: token.content, + apiCpggUrlIv: url.iv, + apiCpggUrlContent: url.content, + }, + }); + + logger.silly(createGuild); + + logger?.silly(`Updated API credentials.`); + + const interactionEmbed = new EmbedBuilder() + .setTitle("[:tools:] CPGG") + .setDescription( + `The following configuration will be used. + +**Scheme**: ${scheme} +**Domain**: ${domain} +**Token**: ends with ${tokenData?.slice(-4)}` + ) + .setColor(successColor) + .setTimestamp() + .setFooter({ + iconURL: footerIcon, + text: footerText, + }); + + await interaction?.editReply({ + embeds: [interactionEmbed], + }); + return; + }, +}; diff --git a/src/commands/config/modules/credits/index.ts b/src/commands/config/modules/credits/index.ts new file mode 100644 index 0000000..70525f2 --- /dev/null +++ b/src/commands/config/modules/credits/index.ts @@ -0,0 +1,165 @@ +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, +} from "discord.js"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import checkPermission from "../../../../helpers/checkPermission"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; + +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("credits") + .setDescription(`Configure this guild's credits module.`) + .addBooleanOption((option) => + option + .setName("enabled") + .setDescription("Do you want to activate the credit module?") + .setRequired(true) + ) + .addNumberOption((option) => + option + .setName("rate") + .setDescription("Credit rate per message.") + .setRequired(true) + .setMinValue(1) + ) + .addNumberOption((option) => + option + .setName("minimum-length") + .setDescription("Minimum message length to receive credit.") + .setRequired(true) + ) + .addNumberOption((option) => + option + .setName("work-rate") + .setDescription( + "The maximum amount of credit that can be obtained within a working day." + ) + .setRequired(true) + .setMinValue(1) + ) + .addNumberOption((option) => + option + .setName("work-timeout") + .setDescription( + "How long you need to wait before you can work again provided in seconds." + ) + .setRequired(true) + ) + .addNumberOption((option) => + option + .setName("timeout") + .setDescription( + "How long you need to wait before you can earn more credits." + ) + .setRequired(true) + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + const { guild, options } = interaction; + + const enabled = options.getBoolean("enabled"); + const rate = options.getNumber("rate"); + const timeout = options.getNumber("timeout"); + const minimumLength = options.getNumber("minimum-length"); + const workRate = options.getNumber("work-rate"); + const workTimeout = options.getNumber("work-timeout"); + + if (!guild) throw new Error("Guild not found."); + if (typeof enabled !== "boolean") + throw new Error("Enabled option is not a boolean."); + if (typeof rate !== "number") throw new Error("Rate is not a number."); + if (typeof workRate !== "number") + throw new Error("Work rate is not a number."); + if (typeof workTimeout !== "number") + throw new Error("Work timeout is not a number."); + if (typeof timeout !== "number") + throw new Error("Timeout is not a number."); + if (typeof minimumLength !== "number") + throw new Error("Minimum length is not a number."); + + const createGuild = await prisma.guild.upsert({ + where: { + id: guild.id, + }, + update: { + creditsEnabled: enabled, + creditsRate: rate, + creditsTimeout: timeout, + creditsWorkRate: workRate, + creditsWorkTimeout: workTimeout, + creditsMinimumLength: minimumLength, + }, + create: { + id: guild.id, + creditsEnabled: enabled, + creditsRate: rate, + creditsTimeout: timeout, + creditsWorkRate: workRate, + creditsWorkTimeout: workTimeout, + creditsMinimumLength: minimumLength, + }, + }); + + logger.silly(createGuild); + + const interactionEmbed = new EmbedBuilder() + .setTitle("[:tools:] Credits") + .setDescription("Credits settings updated") + .setColor(successColor) + .addFields( + { + name: "🤖 Enabled?", + value: `${createGuild.creditsEnabled}`, + inline: true, + }, + { + name: "📈 Rate", + value: `${createGuild.creditsRate}`, + inline: true, + }, + { + name: "📈 Work Rate", + value: `${createGuild.creditsWorkRate}`, + inline: true, + }, + { + name: "🔨 Minimum Length", + value: `${createGuild.creditsMinimumLength}`, + inline: true, + }, + { + name: "⏰ Timeout", + value: `${createGuild.creditsTimeout}`, + inline: true, + }, + { + name: "⏰ Work Timeout", + value: `${createGuild.creditsWorkTimeout}`, + inline: true, + } + ) + .setTimestamp() + .setFooter({ + iconURL: footerIcon, + text: footerText, + }); + + await interaction.editReply({ + embeds: [interactionEmbed], + }); + return; + }, +}; diff --git a/src/commands/config/modules/embeds/components/getValues/index.ts b/src/commands/config/modules/embeds/components/getValues/index.ts new file mode 100644 index 0000000..ae35157 --- /dev/null +++ b/src/commands/config/modules/embeds/components/getValues/index.ts @@ -0,0 +1,56 @@ +import { ChatInputCommandInteraction, ColorResolvable } from "discord.js"; +import prisma from "../../../../../../handlers/database"; +import getEmbedConfig from "../../../../../../helpers/getEmbedData"; +import logger from "../../../../../../middlewares/logger"; + +export default async (interaction: ChatInputCommandInteraction) => { + const { options, guild } = interaction; + + if (!guild) throw new Error("Guild not found"); + + const embedConfig = await getEmbedConfig(guild); + if (!embedConfig) throw new Error("Embed config not found"); + + const newSuccessColor = options.getString("success-color"); + const newWaitColor = options.getString("wait-color"); + const newErrorColor = options.getString("error-color"); + const newFooterIcon = options.getString("footer-icon"); + const newFooterText = options.getString("footer-text"); + + if (!newSuccessColor) throw new Error("Success color not found"); + if (!newWaitColor) throw new Error("Wait color not found"); + if (!newErrorColor) throw new Error("Error color not found"); + if (!newFooterIcon) throw new Error("Footer icon not found"); + if (!newFooterText) throw new Error("Footer text not found"); + + const createGuild = await prisma.guild.upsert({ + where: { + id: guild.id, + }, + update: { + embedColorSuccess: newSuccessColor, + embedColorWait: newWaitColor, + embedColorError: newErrorColor, + embedFooterIcon: newFooterIcon, + embedFooterText: newFooterText, + }, + create: { + id: guild.id, + embedColorSuccess: newSuccessColor, + embedColorWait: newWaitColor, + embedColorError: newErrorColor, + embedFooterIcon: newFooterIcon, + embedFooterText: newFooterText, + }, + }); + + logger.silly(createGuild); + + const successColor = createGuild.embedColorSuccess; + const waitColor = createGuild.embedColorWait; + const errorColor = createGuild.embedColorError; + const footerText = createGuild.embedFooterText; + const footerIcon = createGuild.embedFooterIcon; + + return { successColor, waitColor, errorColor, footerText, footerIcon }; +}; diff --git a/src/commands/config/modules/embeds/index.ts b/src/commands/config/modules/embeds/index.ts new file mode 100644 index 0000000..a4c8195 --- /dev/null +++ b/src/commands/config/modules/embeds/index.ts @@ -0,0 +1,100 @@ +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, +} from "discord.js"; + +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import deferReply from "../../../../handlers/deferReply"; +import checkPermission from "../../../../helpers/checkPermission"; +import getValues from "./components/getValues"; + +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("embeds") + .setDescription(`Embeds`) + .addStringOption((option) => + option + .setName("success-color") + .setDescription("No provided description") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("wait-color") + .setDescription("No provided description") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("error-color") + .setDescription("No provided description") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("footer-icon") + .setDescription("No provided description") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("footer-text") + .setDescription("No provided description") + .setRequired(true) + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { guild } = interaction; + if (!guild) throw new Error("Guild not found"); + + const { successColor, waitColor, errorColor, footerText, footerIcon } = + await getValues(interaction); + + const embed = new EmbedBuilder() + .setTitle("[:tools:] Embeds") + .setFooter({ text: footerText, iconURL: footerIcon }) + .setTimestamp(new Date()); + + embed + .setDescription("Following embed configuration will be used.") + .setColor(successColor) + .addFields([ + { + name: "🟢 Success Color", + value: `${successColor}`, + inline: true, + }, + { + name: "🟡 Wait Color", + value: `${waitColor}`, + inline: true, + }, + { + name: "🔴 Error Color", + value: `${errorColor}`, + inline: true, + }, + { + name: "🖼️ Footer Icon", + value: `${footerIcon}`, + inline: true, + }, + { + name: "📄 Footer Text", + value: `${footerText}`, + inline: true, + }, + ]); + + await interaction.editReply({ + embeds: [embed], + }); + return; + }, +}; diff --git a/src/commands/config/modules/points/index.ts b/src/commands/config/modules/points/index.ts new file mode 100644 index 0000000..8761bbe --- /dev/null +++ b/src/commands/config/modules/points/index.ts @@ -0,0 +1,123 @@ +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, +} from "discord.js"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import checkPermission from "../../../../helpers/checkPermission"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; + +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("points") + .setDescription("Points") + .addBooleanOption((option) => + option + .setName("status") + .setDescription("Should credits be enabled?") + .setRequired(true) + ) + .addNumberOption((option) => + option + .setName("rate") + .setDescription("Amount of credits per message.") + .setRequired(true) + ) + .addNumberOption((option) => + option + .setName("minimum-length") + .setDescription("Minimum length of message to earn credits.") + .setRequired(true) + ) + .addNumberOption((option) => + option + .setName("timeout") + .setDescription("Timeout between earning credits (milliseconds).") + .setRequired(true) + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + + const { options, guild } = interaction; + + const status = options?.getBoolean("status"); + const rate = options?.getNumber("rate"); + const timeout = options?.getNumber("timeout"); + const minimumLength = options?.getNumber("minimum-length"); + + if (!guild) throw new Error("Guild is required"); + if (status === null) throw new Error("Status must be specified"); + if (!rate) throw new Error("Rate must be specified"); + if (!timeout) throw new Error("Timeout must be specified"); + if (!minimumLength) throw new Error("Minimum length must be specified"); + + const createGuild = await prisma.guild.upsert({ + where: { + id: guild.id, + }, + update: { + pointsEnabled: status, + pointsRate: rate, + pointsTimeout: timeout, + pointsMinimumLength: minimumLength, + }, + create: { + id: guild.id, + pointsEnabled: status, + pointsRate: rate, + pointsTimeout: timeout, + pointsMinimumLength: minimumLength, + }, + }); + + logger.silly(createGuild); + + const interactionEmbed = new EmbedBuilder() + .setTitle("[:tools:] Points") + .setDescription("Points settings updated") + .setColor(successColor) + .addFields( + { + name: "🤖 Status", + value: `${createGuild.pointsEnabled}`, + inline: true, + }, + { + name: "📈 Rate", + value: `${createGuild.pointsRate}`, + inline: true, + }, + { + name: "🔨 Minimum Length", + value: `${createGuild.pointsMinimumLength}`, + inline: true, + }, + { + name: "⏰ Timeout", + value: `${createGuild.pointsTimeout}`, + inline: true, + } + ) + .setTimestamp() + .setFooter({ + iconURL: footerIcon, + text: footerText, + }); + + await interaction?.editReply({ + embeds: [interactionEmbed], + }); + return; + }, +}; diff --git a/src/commands/config/modules/shop/index.ts b/src/commands/config/modules/shop/index.ts new file mode 100644 index 0000000..577867b --- /dev/null +++ b/src/commands/config/modules/shop/index.ts @@ -0,0 +1,93 @@ +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, +} from "discord.js"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import checkPermission from "../../../../helpers/checkPermission"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; + +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("shop") + .setDescription("Shop") + .addBooleanOption((option) => + option + .setName("roles-status") + .setDescription("Should roles be enabled?") + .setRequired(true) + ) + .addNumberOption((option) => + option + .setName("roles-price-per-hour") + .setDescription("Price per hour for roles.") + .setRequired(true) + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + const { options, guild } = interaction; + + const rolesStatus = options?.getBoolean("roles-status"); + const rolesPricePerHour = options?.getNumber("roles-price-per-hour"); + + if (!guild) throw new Error("Guild not found"); + if (rolesStatus === null) throw new Error("Status must be provided"); + if (!rolesPricePerHour) + throw new Error("Roles price per hour must be provided"); + + const createGuild = await prisma.guild.upsert({ + where: { + id: guild.id, + }, + update: { + shopRolesEnabled: rolesStatus, + shopRolesPricePerHour: rolesPricePerHour, + }, + create: { + id: guild.id, + shopRolesEnabled: rolesStatus, + shopRolesPricePerHour: rolesPricePerHour, + }, + }); + + logger.silly(createGuild); + + const interactionEmbed = new EmbedBuilder() + .setTitle("[:tools:] Shop") + .setDescription("Shop settings updated") + .setColor(successColor) + .addFields( + { + name: "🤖 Roles Status", + value: `${createGuild.shopRolesEnabled}`, + inline: true, + }, + { + name: "🌊 Roles Price Per Hour", + value: `${createGuild.shopRolesPricePerHour}`, + inline: true, + } + ) + .setTimestamp() + .setFooter({ + iconURL: footerIcon, + text: footerText, + }); + + await interaction?.editReply({ + embeds: [interactionEmbed], + }); + return; + }, +}; diff --git a/src/commands/config/modules/welcome/index.ts b/src/commands/config/modules/welcome/index.ts new file mode 100644 index 0000000..0ac56e1 --- /dev/null +++ b/src/commands/config/modules/welcome/index.ts @@ -0,0 +1,147 @@ +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { ChannelType } from "discord-api-types/v10"; +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, +} from "discord.js"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import checkPermission from "../../../../helpers/checkPermission"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; + +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("welcome") + .setDescription("Welcome") + .addBooleanOption((option) => + option + .setName("status") + .setDescription("Should welcome be enabled?") + .setRequired(true) + ) + .addChannelOption((option) => + option + .setName("join-channel") + .setDescription("Channel for join messages.") + .addChannelTypes(ChannelType.GuildText) + .setRequired(true) + ) + + .addChannelOption((option) => + option + .setName("leave-channel") + .setDescription("Channel for leave messages.") + .addChannelTypes(ChannelType.GuildText) + .setRequired(true) + ) + + .addStringOption((option) => + option + .setName("leave-message") + .setDescription("Message for leave messages.") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("join-message") + .setDescription("Message for join messages.") + .setRequired(true) + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + const { options, guild } = interaction; + + const status = options?.getBoolean("status"); + const joinChannel = options?.getChannel("join-channel"); + const leaveChannel = options?.getChannel("leave-channel"); + const joinChannelMessage = options?.getString("join-message"); + const leaveChannelMessage = options?.getString("leave-message"); + + if (!guild) throw new Error("Guild not found"); + if (status === null) throw new Error("Status not specified"); + if (!joinChannel) throw new Error("Join channel not specified"); + if (!joinChannelMessage) + throw new Error("Join channel message not specified"); + if (!leaveChannel) throw new Error("Leave channel not specified"); + if (!leaveChannelMessage) + throw new Error("Leave channel message not specified"); + + const createGuild = await prisma.guild.upsert({ + where: { + id: guild.id, + }, + update: { + welcomeEnabled: status, + welcomeJoinChannelId: joinChannel.id, + welcomeJoinChannelMessage: joinChannelMessage, + welcomeLeaveChannelId: leaveChannel.id, + welcomeLeaveChannelMessage: leaveChannelMessage, + }, + create: { + id: guild.id, + welcomeEnabled: status, + welcomeJoinChannelId: joinChannel.id, + welcomeJoinChannelMessage: joinChannelMessage, + welcomeLeaveChannelId: leaveChannel.id, + welcomeLeaveChannelMessage: leaveChannelMessage, + }, + }); + + logger.silly(createGuild); + + const interactionEmbedDisabled = new EmbedBuilder() + .setTitle("[:tools:] Welcome") + .setDescription( + "This module is currently disabled, please enable it to continue." + ) + .setColor(successColor) + .setTimestamp() + .setFooter({ + iconURL: footerIcon, + text: footerText, + }); + + if (!createGuild.welcomeEnabled) { + return interaction?.editReply({ + embeds: [interactionEmbedDisabled], + }); + } + + const interactionEmbed = new EmbedBuilder() + .setTitle("[:tools:] Welcome") + .setDescription( + `The following configuration will be used. + + [👋] **Welcome** + + ㅤ**Channel**: <#${createGuild.welcomeJoinChannelId}> + ㅤ**Message**: ${createGuild.welcomeJoinChannelMessage} + + [🚪] **Leave** + + ㅤ**Channel**: <#${createGuild.welcomeLeaveChannelId}> + ㅤ**Message**: ${createGuild.welcomeLeaveChannelMessage}` + ) + .setColor(successColor) + .setTimestamp() + .setFooter({ + iconURL: footerIcon, + text: footerText, + }); + + await interaction?.editReply({ + embeds: [interactionEmbed], + }); + return; + }, +}; diff --git a/src/commands/counters/index.ts b/src/commands/counters/index.ts new file mode 100644 index 0000000..5abd43e --- /dev/null +++ b/src/commands/counters/index.ts @@ -0,0 +1,24 @@ +import { SlashCommandBuilder } from "@discordjs/builders"; +import { ChatInputCommandInteraction } from "discord.js"; + +// Modules +import moduleView from "./modules/view"; + +export const builder = new SlashCommandBuilder() + .setName("counters") + .setDescription("View guild counters") + .setDMPermission(false) + + // Modules + .addSubcommand(moduleView.builder); + +// Execute function +export const execute = async (interaction: ChatInputCommandInteraction) => { + switch (interaction.options.getSubcommand()) { + case "view": + await moduleView.execute(interaction); + break; + default: + throw new Error("No module found for that command."); + } +}; diff --git a/src/commands/counters/modules/view/index.ts b/src/commands/counters/modules/view/index.ts new file mode 100644 index 0000000..76611f8 --- /dev/null +++ b/src/commands/counters/modules/view/index.ts @@ -0,0 +1,67 @@ +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { ChannelType } from "discord-api-types/v10"; +import { ChatInputCommandInteraction, EmbedBuilder } from "discord.js"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; + +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("view") + .setDescription(`View a guild counter`) + .addChannelOption((option) => + option + .setName("channel") + .setDescription( + `The channel that contains the counter you want to view` + ) + .setRequired(true) + .addChannelTypes(ChannelType.GuildText) + ); + }, + + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, false); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + const { options, guild } = interaction; + + const discordChannel = options.getChannel("channel"); + + if (!guild) throw new Error(`Guild not found`); + if (!discordChannel) throw new Error(`Channel not found`); + + const embed = new EmbedBuilder() + .setTitle("[:1234:] Counters (View)") + .setTimestamp(new Date()) + .setFooter({ + text: footerText, + iconURL: footerIcon, + }); + + const channelCounter = await prisma.guildCounter.findUnique({ + where: { + guildId_channelId: { + guildId: guild.id, + channelId: discordChannel.id, + }, + }, + }); + + if (!channelCounter) throw new Error("No counter found for channel"); + + await interaction.editReply({ + embeds: [ + embed + .setDescription( + `Viewing counter for channel ${discordChannel}: ${channelCounter.count}!` + ) + .setColor(successColor), + ], + }); + return; + }, +}; diff --git a/src/commands/credits/index.ts b/src/commands/credits/index.ts new file mode 100644 index 0000000..777d32c --- /dev/null +++ b/src/commands/credits/index.ts @@ -0,0 +1,42 @@ +import { SlashCommandBuilder } from "@discordjs/builders"; +import { ChatInputCommandInteraction } from "discord.js"; +import logger from "../../middlewares/logger"; + +// Modules +import moduleBalance from "./modules/balance"; +import moduleGift from "./modules/gift"; +import moduleTop from "./modules/top"; +import moduleWork from "./modules/work"; + +export const builder = new SlashCommandBuilder() + .setName("credits") + .setDescription("Manage your credits.") + .setDMPermission(false) + + // Modules + .addSubcommand(moduleBalance.builder) + .addSubcommand(moduleGift.builder) + .addSubcommand(moduleTop.builder) + .addSubcommand(moduleWork.builder); + +// Execute function +export const execute = async (interaction: ChatInputCommandInteraction) => { + const { options } = interaction; + + switch (options.getSubcommand()) { + case "balance": + await moduleBalance.execute(interaction); + break; + case "gift": + await moduleGift.execute(interaction); + break; + case "top": + await moduleTop.execute(interaction); + break; + case "work": + await moduleWork.execute(interaction); + break; + default: + logger.silly(`Unknown subcommand ${options.getSubcommand()}`); + } +}; diff --git a/src/commands/credits/modules/balance/index.ts b/src/commands/credits/modules/balance/index.ts new file mode 100644 index 0000000..9034f63 --- /dev/null +++ b/src/commands/credits/modules/balance/index.ts @@ -0,0 +1,95 @@ +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { CommandInteraction, EmbedBuilder } from "discord.js"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; + +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("balance") + .setDescription(`View a user's balance`) + .addUserOption((option) => + option + .setName("user") + .setDescription(`The user whose balance you want to view`) + ); + }, + execute: async (interaction: CommandInteraction) => { + await deferReply(interaction, true); + + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(interaction.guild); + const { options, user, guild } = interaction; + + const discordUser = options.getUser("user"); + + const embed = new EmbedBuilder() + .setTitle("[:dollar:] Balance") + .setTimestamp(new Date()) + .setFooter({ text: footerText, iconURL: footerIcon }); + + if (guild === null) { + logger.silly(`Guild is null`); + + return interaction.editReply({ + embeds: [ + embed.setDescription("Guild is not found").setColor(errorColor), + ], + }); + } + + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: (discordUser || user).id, + guildId: guild.id, + }, + }, + update: {}, + create: { + user: { + connectOrCreate: { + create: { + id: (discordUser || user).id, + }, + where: { + id: (discordUser || user).id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, + include: { + user: true, + guild: true, + }, + }); + + logger.silly(createGuildMember); + + if (!createGuildMember) throw new Error("No guild member exists."); + + return interaction.editReply({ + embeds: [ + embed + .setDescription( + `${discordUser || user} currently has ${ + createGuildMember.creditsEarned + } credits.` + ) + .setColor(successColor), + ], + }); + }, +}; diff --git a/src/commands/credits/modules/gift/index.ts b/src/commands/credits/modules/gift/index.ts new file mode 100644 index 0000000..db3435c --- /dev/null +++ b/src/commands/credits/modules/gift/index.ts @@ -0,0 +1,89 @@ +// Dependencies +// Models +import { + ChatInputCommandInteraction, + EmbedBuilder, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import transferCredits from "../../../../helpers/transferCredits"; +// Configurations +import deferReply from "../../../../handlers/deferReply"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +// Handlers + +// Function +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("gift") + .setDescription(`Gift a user credits`) + .addUserOption((option) => + option + .setName("user") + .setDescription("The user you want to gift credits to.") + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName("amount") + .setDescription("The amount of credits you want to gift.") + .setRequired(true) + ) + .addStringOption((option) => + option.setName("reason").setDescription("Your reason.") + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + const { options, user, guild, client } = interaction; + + const optionUser = options.getUser("user"); + const optionAmount = options.getInteger("amount"); + const optionReason = options.getString("reason"); + + const embed = new EmbedBuilder() + .setTitle("[:dollar:] Gift") + .setTimestamp(new Date()) + .setFooter({ text: footerText, iconURL: footerIcon }); + + if (!guild) throw new Error("Guild not found"); + if (!optionUser) throw new Error("No receiver found"); + if (optionAmount === null) throw new Error("Amount not found"); + + await transferCredits(guild, user, optionUser, optionAmount); + + // Get DM user object + const dmUser = client.users.cache.get(optionUser.id); + + if (!dmUser) throw new Error("User not found"); + + // Send DM to user + await dmUser.send({ + embeds: [ + embed + .setDescription( + `${user.tag} has gifted you ${optionAmount} credits with reason: ${ + optionReason || "unspecified" + }` + ) + .setColor(successColor), + ], + }); + + return interaction.editReply({ + embeds: [ + embed + .setDescription( + `Successfully gifted ${optionAmount} credits to ${optionUser} with reason: ${ + optionReason || "unspecified" + }` + ) + .setColor(successColor), + ], + }); + }, +}; diff --git a/src/plugins/commands/credits/modules/top/index.ts b/src/commands/credits/modules/top/index.ts similarity index 56% rename from src/plugins/commands/credits/modules/top/index.ts rename to src/commands/credits/modules/top/index.ts index 54478aa..075c599 100644 --- a/src/plugins/commands/credits/modules/top/index.ts +++ b/src/commands/credits/modules/top/index.ts @@ -1,10 +1,10 @@ -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - -import { CommandInteraction, MessageEmbed } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import logger from "../../../../../logger"; - -import userSchema, { IUser } from "../../../../../models/user"; +import { GuildMember } from "@prisma/client"; +import { CommandInteraction, EmbedBuilder } from "discord.js"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; export default { metadata: { guildOnly: true, ephemeral: false }, @@ -13,11 +13,13 @@ export default { return command.setName("top").setDescription(`View the top users`); }, execute: async (interaction: CommandInteraction) => { + await deferReply(interaction, false); + const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); const { guild } = interaction; - const embed = new MessageEmbed() + const embed = new EmbedBuilder() .setTitle("[:dollar:] Top") .setTimestamp(new Date()) .setFooter({ text: footerText, iconURL: footerIcon }); @@ -36,19 +38,25 @@ export default { }); } - const usersDB = await userSchema.find({ guildId: guild.id }); + // const usersDB = await userSchema.find({ guildId: guild.id }); - const topTen = usersDB + const topTen = await prisma.guildMember.findMany({ + where: { + guildId: guild.id, + }, + orderBy: { + creditsEarned: "desc", + }, + take: 10, + }); - // Sort them after credits amount (ascending) - .sort((a, b) => (a.credits > b.credits ? -1 : 1)) - - // Return the top 10 - .slice(0, 10); + logger.silly(topTen); // Create entry object - const entry = (x: IUser, index: number) => - `${index + 1}. <@${x.userId}> - ${x.credits} credits`; + const entry = (guildMember: GuildMember, index: number) => + `${index + 1}. <@${guildMember.userId}> - ${ + guildMember.creditsEarned + } credits`; return interaction.editReply({ embeds: [ @@ -56,8 +64,7 @@ export default { .setDescription( `Below are the top ten members in this guild. - ${topTen.map(entry).join("\n")} - ` + ${topTen.map(entry).join("\n")}` ) .setColor(successColor), ], diff --git a/src/commands/credits/modules/work/index.ts b/src/commands/credits/modules/work/index.ts new file mode 100644 index 0000000..a69d155 --- /dev/null +++ b/src/commands/credits/modules/work/index.ts @@ -0,0 +1,108 @@ +// Dependencies +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import Chance from "chance"; +import { CommandInteraction, EmbedBuilder } from "discord.js"; +// Models +import { command as CooldownCommand } from "../../../../handlers/cooldown"; +// Configurations +import getEmbedConfig from "../../../../helpers/getEmbedData"; +// Helpers +// Handlers +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import logger from "../../../../middlewares/logger"; + +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command.setName("work").setDescription(`Work to earn credits`); + }, + execute: async (interaction: CommandInteraction) => { + await deferReply(interaction, true); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); // Destructure member + const { guild, user } = interaction; + + const embed = new EmbedBuilder() + .setTitle("[:dollar:] Work") + .setTimestamp(new Date()) + .setFooter({ + text: footerText, + iconURL: footerIcon, + }); + + // Chance module + const chance = new Chance(); + + if (guild === null) { + return logger?.silly(`Guild is null`); + } + + const createGuild = await prisma.guild.upsert({ + where: { + id: guild.id, + }, + update: {}, + create: { + id: guild.id, + }, + }); + + logger.silly(createGuild); + + await CooldownCommand(interaction, createGuild.creditsWorkTimeout); + + const creditsEarned = chance.integer({ + min: 0, + max: createGuild.creditsWorkRate, + }); + + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: user.id, + guildId: guild.id, + }, + }, + update: { creditsEarned: { increment: creditsEarned } }, + create: { + creditsEarned, + user: { + connectOrCreate: { + create: { + id: user.id, + }, + where: { + id: user.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, + include: { + user: true, + guild: true, + }, + }); + + logger.silly(createGuildMember); + + return interaction.editReply({ + embeds: [ + embed + .setDescription(`You worked and earned ${creditsEarned} credits.`) + .setColor(successColor), + ], + }); + }, +}; diff --git a/src/plugins/commands/dns/index.ts b/src/commands/dns/index.ts similarity index 51% rename from src/plugins/commands/dns/index.ts rename to src/commands/dns/index.ts index ee623ec..cd5f9ec 100644 --- a/src/plugins/commands/dns/index.ts +++ b/src/commands/dns/index.ts @@ -1,19 +1,22 @@ import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; +import { ChatInputCommandInteraction } from "discord.js"; -import modules from "./modules"; -export const moduleData = modules; +// Modules +import moduleLookup from "./modules/lookup"; export const builder = new SlashCommandBuilder() .setName("dns") .setDescription("DNS commands.") - .addSubcommand(modules.lookup.builder); + // Modules + .addSubcommand(moduleLookup.builder); -export const execute = async (interaction: CommandInteraction) => { +// Execute the command +export const execute = async (interaction: ChatInputCommandInteraction) => { switch (interaction.options.getSubcommand()) { case "lookup": - return modules.lookup.execute(interaction); + await moduleLookup.execute(interaction); + break; default: throw new Error( `Unknown subcommand: ${interaction.options.getSubcommand()}` diff --git a/src/plugins/commands/dns/modules/lookup/index.ts b/src/commands/dns/modules/lookup/index.ts similarity index 91% rename from src/plugins/commands/dns/modules/lookup/index.ts rename to src/commands/dns/modules/lookup/index.ts index ea56b83..b64c18f 100644 --- a/src/plugins/commands/dns/modules/lookup/index.ts +++ b/src/commands/dns/modules/lookup/index.ts @@ -1,13 +1,10 @@ -import axios from "axios"; -import { CommandInteraction, MessageEmbed } from "discord.js"; - -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import axios from "axios"; +import { ChatInputCommandInteraction, EmbedBuilder } from "discord.js"; +import deferReply from "../../../../handlers/deferReply"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; export default { - metadata: { guildOnly: false, ephemeral: false }, - builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("lookup") @@ -21,7 +18,9 @@ export default { .setRequired(true) ); }, - execute: async (interaction: CommandInteraction) => { + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, false); + const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); const embedTitle = "[:hammer:] Utility (Lookup)"; @@ -35,7 +34,7 @@ export default { if (response.data.status !== "success") { await interaction.editReply({ embeds: [ - new MessageEmbed() + new EmbedBuilder() .setTitle(embedTitle) .setFooter({ text: footerText, @@ -54,7 +53,7 @@ export default { await interaction.editReply({ embeds: [ - new MessageEmbed() + new EmbedBuilder() .setTitle(embedTitle) .setFooter({ text: footerText, diff --git a/src/commands/fun/index.ts b/src/commands/fun/index.ts new file mode 100644 index 0000000..e4f0972 --- /dev/null +++ b/src/commands/fun/index.ts @@ -0,0 +1,23 @@ +import { SlashCommandBuilder } from "@discordjs/builders"; +import { ChatInputCommandInteraction } from "discord.js"; +import logger from "../../middlewares/logger"; + +// Modules +import moduleMeme from "./modules/meme"; + +export const builder = new SlashCommandBuilder() + .setName("fun") + .setDescription("Fun commands.") + + .addSubcommand(moduleMeme.builder); + +// Execute function +export const execute = async (interaction: ChatInputCommandInteraction) => { + const { options } = interaction; + + if (options.getSubcommand() === "meme") { + await moduleMeme.execute(interaction); + } else { + logger.silly(`Unknown subcommand ${options.getSubcommand()}`); + } +}; diff --git a/src/plugins/commands/fun/modules/meme/index.ts b/src/commands/fun/modules/meme/index.ts similarity index 77% rename from src/plugins/commands/fun/modules/meme/index.ts rename to src/commands/fun/modules/meme/index.ts index 88b2304..1ed1ea0 100644 --- a/src/plugins/commands/fun/modules/meme/index.ts +++ b/src/commands/fun/modules/meme/index.ts @@ -1,17 +1,20 @@ -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - -import axios from "axios"; -import { CommandInteraction, MessageEmbed } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import axios from "axios"; +import { CommandInteraction, EmbedBuilder } from "discord.js"; +import { command as CooldownCommand } from "../../../../handlers/cooldown"; +import deferReply from "../../../../handlers/deferReply"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; export default { - metadata: { guildOnly: false, ephemeral: false, cooldown: 15 }, - builder: (command: SlashCommandSubcommandBuilder) => { return command.setName("meme").setDescription("Get a meme from r/memes)"); }, execute: async (interaction: CommandInteraction) => { + await deferReply(interaction, false); + + await CooldownCommand(interaction, 15); + const { guild } = interaction; const embedConfig = await getEmbedConfig(guild); @@ -22,7 +25,7 @@ export default { const response = res.data[0].data.children; const content = response[0].data; - const embed = new MessageEmbed() + const embed = new EmbedBuilder() .setAuthor({ name: content.title, iconURL: @@ -50,7 +53,8 @@ export default { }) .setColor(embedConfig.successColor); - return interaction.editReply({ embeds: [embed] }); + await interaction.editReply({ embeds: [embed] }); + return; }) .catch((error) => { throw new Error(error.message); diff --git a/src/commands/manage/index.ts b/src/commands/manage/index.ts new file mode 100644 index 0000000..0062de7 --- /dev/null +++ b/src/commands/manage/index.ts @@ -0,0 +1,36 @@ +//Dependencies +import { SlashCommandBuilder } from "@discordjs/builders"; +import { ChatInputCommandInteraction } from "discord.js"; + +// Modules +import moduleCounters from "./modules/counters"; +import moduleCredits from "./modules/credits"; + +// Function +export const builder = new SlashCommandBuilder() + .setName("manage") + .setDescription("Manage the bot.") + .setDMPermission(false) + + // Modules + .addSubcommandGroup(moduleCounters.builder) + .addSubcommandGroup(moduleCredits.builder); + +export const execute = async (interaction: ChatInputCommandInteraction) => { + // Destructure + const { options } = interaction; + + switch (options.getSubcommandGroup()) { + case "credits": { + await moduleCredits.execute(interaction); + break; + } + case "counters": { + await moduleCounters.execute(interaction); + break; + } + default: { + throw new Error("Could not find an module for the command."); + } + } +}; diff --git a/src/commands/manage/modules/counters/index.ts b/src/commands/manage/modules/counters/index.ts new file mode 100644 index 0000000..7412a09 --- /dev/null +++ b/src/commands/manage/modules/counters/index.ts @@ -0,0 +1,34 @@ +// Dependencies +import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; +import { ChatInputCommandInteraction } from "discord.js"; + +// Modules +import moduleAdd from "./modules/add"; +import moduleRemove from "./modules/remove"; + +export default { + builder: (group: SlashCommandSubcommandGroupBuilder) => { + return group + .setName("counters") + .setDescription("Manage guild counters.") + .addSubcommand(moduleAdd.builder) + .addSubcommand(moduleRemove.builder); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + const { options } = interaction; + + switch (options.getSubcommand()) { + case "add": { + await moduleAdd.execute(interaction); + break; + } + case "remove": { + await moduleRemove.execute(interaction); + break; + } + default: { + throw new Error("Could not found a module for that command."); + } + } + }, +}; diff --git a/src/commands/manage/modules/counters/modules/add/index.ts b/src/commands/manage/modules/counters/modules/add/index.ts new file mode 100644 index 0000000..9c3dafe --- /dev/null +++ b/src/commands/manage/modules/counters/modules/add/index.ts @@ -0,0 +1,113 @@ +// Dependencies +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { ChannelType } from "discord-api-types/v10"; +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, +} from "discord.js"; +import deferReply from "../../../../../../handlers/deferReply"; +import checkPermission from "../../../../../../helpers/checkPermission"; +// Configurations +import prisma from "../../../../../../handlers/database"; +import getEmbedConfig from "../../../../../../helpers/getEmbedData"; +import logger from "../../../../../../middlewares/logger"; + +// Function +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("add") + .setDescription("Add a counter to your guild.") + .addChannelOption((option) => + option + .setName("channel") + .setDescription("The channel to send the counter to.") + .setRequired(true) + .addChannelTypes(ChannelType.GuildText) + ) + .addStringOption((option) => + option + .setName("word") + .setDescription("The word to use for the counter.") + .setRequired(true) + ) + .addNumberOption((option) => + option + .setName("start") + .setDescription("The starting value of the counter.") + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + const { options, guild } = interaction; + + const discordChannel = options?.getChannel("channel"); + const triggerWord = options?.getString("word"); + const startValue = options?.getNumber("start"); + + if (!guild) throw new Error("We could not find a guild"); + if (!discordChannel) throw new Error("We could not find a channel"); + if (!triggerWord) throw new Error("We could not find a word"); + + const channelCounter = await prisma.guildCounter.findUnique({ + where: { + guildId_channelId: { + guildId: guild.id, + channelId: discordChannel.id, + }, + }, + }); + + if (channelCounter) + throw new Error("A counter already exists for this channel."); + + const createGuildCounter = await prisma.guildCounter.upsert({ + where: { + guildId_channelId: { + guildId: guild.id, + channelId: discordChannel.id, + }, + }, + update: {}, + create: { + channelId: discordChannel.id, + triggerWord, + count: startValue || 0, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, + }); + + logger.silly(createGuildCounter); + + if (createGuildCounter) { + const embed = new EmbedBuilder() + .setTitle("[:toolbox:] Counters - Add") + .setTimestamp(new Date()) + .setFooter({ text: footerText, iconURL: footerIcon }); + + await interaction?.editReply({ + embeds: [ + embed + .setDescription(":white_check_mark: Counter created successfully.") + .setColor(successColor), + ], + }); + } + }, +}; diff --git a/src/commands/manage/modules/counters/modules/remove/index.ts b/src/commands/manage/modules/counters/modules/remove/index.ts new file mode 100644 index 0000000..742ec9a --- /dev/null +++ b/src/commands/manage/modules/counters/modules/remove/index.ts @@ -0,0 +1,82 @@ +// Dependencies +// Models +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { ChannelType } from "discord-api-types/v10"; +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, +} from "discord.js"; +import deferReply from "../../../../../../handlers/deferReply"; +import checkPermission from "../../../../../../helpers/checkPermission"; +// Configurations +import prisma from "../../../../../../handlers/database"; +import getEmbedConfig from "../../../../../../helpers/getEmbedData"; + +// Function +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("remove") + .setDescription(`Delete a counter from your guild.`) + .addChannelOption((option) => + option + .setName("channel") + .setDescription("The channel to delete the counter from.") + .setRequired(true) + .addChannelTypes(ChannelType.GuildText) + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + const { options, guild } = interaction; + + const discordChannel = options?.getChannel("channel"); + + if (!guild) throw new Error("We could not find a guild"); + if (!discordChannel) throw new Error("We could not find a channel"); + + const embed = new EmbedBuilder() + .setTitle("[:toolbox:] Counters - Remove") + .setTimestamp(new Date()) + .setFooter({ text: footerText, iconURL: footerIcon }); + + const channelCounter = await prisma.guildCounter.findUnique({ + where: { + guildId_channelId: { + guildId: guild.id, + channelId: discordChannel.id, + }, + }, + }); + + if (!channelCounter) + throw new Error( + "There is no counter sin this channel, please add one first." + ); + + const deleteGuildCounter = await prisma.guildCounter.deleteMany({ + where: { + guildId: guild.id, + channelId: discordChannel.id, + }, + }); + + if (!deleteGuildCounter) + throw new Error("We could not find a counter for this guild"); + + await interaction?.editReply({ + embeds: [ + embed + .setDescription(":white_check_mark: Counter deleted successfully.") + .setColor(successColor), + ], + }); + }, +}; diff --git a/src/commands/manage/modules/credits/index.ts b/src/commands/manage/modules/credits/index.ts new file mode 100644 index 0000000..681cc24 --- /dev/null +++ b/src/commands/manage/modules/credits/index.ts @@ -0,0 +1,43 @@ +import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; +import { ChatInputCommandInteraction } from "discord.js"; + +// Modules +import moduleGive from "./modules/give"; +import moduleGiveaway from "./modules/giveaway"; +import moduleSet from "./modules/set"; +import moduleTake from "./modules/take"; +import moduleTransfer from "./modules/transfer"; + +export default { + builder: (group: SlashCommandSubcommandGroupBuilder) => { + return group + .setName("credits") + .setDescription("Manage the credits of a user.") + .addSubcommand(moduleGive.builder) + .addSubcommand(moduleSet.builder) + .addSubcommand(moduleTake.builder) + .addSubcommand(moduleTransfer.builder) + .addSubcommand(moduleGiveaway.builder); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + switch (interaction.options.getSubcommand()) { + case "give": + await moduleGive.execute(interaction); + break; + case "set": + await moduleSet.execute(interaction); + break; + case "take": + await moduleTake.execute(interaction); + break; + case "transfer": + await moduleTransfer.execute(interaction); + break; + case "giveaway": + await moduleGiveaway.execute(interaction); + break; + default: + throw new Error("No module found for that specific command"); + } + }, +}; diff --git a/src/commands/manage/modules/credits/modules/give/index.ts b/src/commands/manage/modules/credits/modules/give/index.ts new file mode 100644 index 0000000..f2c070b --- /dev/null +++ b/src/commands/manage/modules/credits/modules/give/index.ts @@ -0,0 +1,114 @@ +// Dependencies +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, +} from "discord.js"; +import logger from "../../../../../../middlewares/logger"; +// Configurations +import getEmbedConfig from "../../../../../../helpers/getEmbedData"; +// Helpers../../../../../../../helpers/userData +import pluralize from "../../../../../../helpers/pluralize"; +// Models +// Handlers +import prisma from "../../../../../../handlers/database"; +import deferReply from "../../../../../../handlers/deferReply"; +import checkPermission from "../../../../../../helpers/checkPermission"; +// Function +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("give") + .setDescription("Give credits to a user.") + .addUserOption((option) => + option + .setName("user") + .setDescription("The user to give credits to.") + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName("amount") + .setDescription(`The amount of credits to give.`) + .setRequired(true) + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); // Destructure + const { guild, options } = interaction; + + const discordReceiver = options?.getUser("user"); + const creditAmount = options?.getInteger("amount"); + + // If amount option is null + if (creditAmount === null) + throw new Error("You need to provide a credit amount."); + + // If amount is zero or below + if (creditAmount <= 0) + throw new Error("You must provide a credit amount greater than zero"); + + if (discordReceiver === null) + throw new Error("We could not get the receiving user from Discord"); + + if (guild === null) + throw new Error("We could not get the current guild from discord."); + + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: discordReceiver.id, + guildId: guild.id, + }, + }, + update: { creditsEarned: { increment: creditAmount } }, + create: { + creditsEarned: creditAmount, + user: { + connectOrCreate: { + create: { + id: discordReceiver.id, + }, + where: { + id: discordReceiver.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, + }); + + logger.silly(createGuildMember); + + // Save toUser + await interaction?.editReply({ + embeds: [ + new EmbedBuilder() + .setTitle("[:toolbox:] Manage - Credits (Give)") + .setDescription( + `Successfully gave ${pluralize(creditAmount, "credit")}` + ) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + return; + }, +}; diff --git a/src/plugins/commands/manage/modules/credits/modules/giveaway/index.ts b/src/commands/manage/modules/credits/modules/giveaway/index.ts similarity index 54% rename from src/plugins/commands/manage/modules/credits/modules/giveaway/index.ts rename to src/commands/manage/modules/credits/modules/giveaway/index.ts index 5c08048..9a0518c 100644 --- a/src/plugins/commands/manage/modules/credits/modules/giveaway/index.ts +++ b/src/commands/manage/modules/credits/modules/giveaway/index.ts @@ -1,30 +1,25 @@ // Dependencies -import { - CommandInteraction, - MessageActionRow, - MessageButton, - MessageEmbed, - Permissions, -} from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import { v4 as uuidv4 } from "uuid"; import axios from "axios"; -import apiSchema from "../../../../../../../models/api"; -import encryption from "../../../../../../../handlers/encryption"; - +import { ButtonStyle, ChannelType } from "discord-api-types/v10"; +import { + ActionRowBuilder, + ButtonBuilder, + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, +} from "discord.js"; +import { v4 as uuidv4 } from "uuid"; +import encryption from "../../../../../../helpers/encryption"; // Configurations -import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; - -import { ChannelType } from "discord-api-types/v10"; +import prisma from "../../../../../../handlers/database"; +import deferReply from "../../../../../../handlers/deferReply"; +import checkPermission from "../../../../../../helpers/checkPermission"; +import getEmbedConfig from "../../../../../../helpers/getEmbedData"; +import logger from "../../../../../../middlewares/logger"; // Function export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("giveaway") @@ -49,11 +44,15 @@ export default { .addChannelTypes(ChannelType.GuildText) ); }, - execute: async (interaction: CommandInteraction) => { + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + const { successColor, footerText, footerIcon } = await getEmbedConfig( interaction.guild ); // Destructure - const { guild, options } = interaction; + const { guild, user, options } = interaction; const uses = options?.getInteger("uses"); const creditAmount = options?.getInteger("credit"); @@ -62,25 +61,75 @@ export default { if (!uses) throw new Error("Amount of uses is required."); if (!creditAmount) throw new Error("Amount of credits is required."); if (!channel) throw new Error("Channel is required."); + if (!guild) throw new Error("Guild is required."); - const embed = new MessageEmbed() + const embed = new EmbedBuilder() .setTitle("[:toolbox:] Giveaway") .setFooter({ text: footerText, iconURL: footerIcon }); const code = uuidv4(); - const apiCredentials = await apiSchema?.findOne({ - guildId: guild?.id, + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: user.id, + guildId: guild.id, + }, + }, + update: {}, + create: { + user: { + connectOrCreate: { + create: { + id: user.id, + }, + where: { + id: user.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, + include: { + user: true, + guild: true, + }, }); - if (!apiCredentials) return; + logger.silly(createGuildMember); - const url = encryption.decrypt(apiCredentials?.url); + if ( + !createGuildMember.guild.apiCpggUrlIv || + !createGuildMember.guild.apiCpggUrlContent + ) + throw new Error("No API url available"); + if ( + !createGuildMember.guild.apiCpggTokenIv || + !createGuildMember.guild.apiCpggTokenContent + ) + throw new Error("No API token available"); + + const url = encryption.decrypt({ + iv: createGuildMember.guild.apiCpggUrlIv, + content: createGuildMember.guild.apiCpggUrlContent, + }); const api = axios?.create({ baseURL: `${url}/api/`, headers: { - Authorization: `Bearer ${encryption.decrypt(apiCredentials.token)}`, + Authorization: `Bearer ${encryption.decrypt({ + iv: createGuildMember.guild.apiCpggTokenIv, + content: createGuildMember.guild.apiCpggTokenContent, + })}`, }, }); @@ -102,10 +151,10 @@ export default { ], }); - const buttons = new MessageActionRow().addComponents( - new MessageButton() + const buttons = new ActionRowBuilder().addComponents( + new ButtonBuilder() .setLabel("Redeem it here") - .setStyle("LINK") + .setStyle(ButtonStyle.Link) .setEmoji("🏦") .setURL(`${shopUrl}?voucher=${code}`) ); @@ -114,11 +163,11 @@ export default { if (!discordChannel) return; - if (discordChannel.type !== "GUILD_TEXT") return; + if (discordChannel.type !== ChannelType.GuildText) return; discordChannel.send({ embeds: [ - new MessageEmbed() + new EmbedBuilder() .setTitle("[:parachute:] Credits!") .addFields([ { diff --git a/src/plugins/commands/manage/modules/credits/modules/set/index.ts b/src/commands/manage/modules/credits/modules/set/index.ts similarity index 50% rename from src/plugins/commands/manage/modules/credits/modules/set/index.ts rename to src/commands/manage/modules/credits/modules/set/index.ts index a3be389..10dd37f 100644 --- a/src/plugins/commands/manage/modules/credits/modules/set/index.ts +++ b/src/commands/manage/modules/credits/modules/set/index.ts @@ -1,26 +1,22 @@ // Dependencies -import { CommandInteraction, MessageEmbed, Permissions } from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; - -// Handlers -import logger from "../../../../../../../logger"; - // Helpers - // Models -import fetchUser from "../../../../../../../helpers/fetchUser"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, +} from "discord.js"; +// Configurations +import getEmbedConfig from "../../../../../../helpers/getEmbedData"; +// Handlers +import prisma from "../../../../../../handlers/database"; +import deferReply from "../../../../../../handlers/deferReply"; +import checkPermission from "../../../../../../helpers/checkPermission"; +import logger from "../../../../../../middlewares/logger"; // Function export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("set") @@ -38,7 +34,11 @@ export default { .setRequired(true) ); }, - execute: async (interaction: CommandInteraction) => { + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); const { options, guild } = interaction; @@ -52,7 +52,7 @@ export default { return interaction?.editReply({ embeds: [ - new MessageEmbed() + new EmbedBuilder() .setTitle("[:toolbox:] Manage - Credits (Set)") .setDescription(`You must provide an amount.`) .setTimestamp(new Date()) @@ -67,7 +67,7 @@ export default { return interaction?.editReply({ embeds: [ - new MessageEmbed() + new EmbedBuilder() .setTitle("[:toolbox:] Manage - Credits (Set)") .setDescription(`You must provide a user.`) .setTimestamp(new Date()) @@ -81,7 +81,7 @@ export default { return interaction?.editReply({ embeds: [ - new MessageEmbed() + new EmbedBuilder() .setTitle("[:toolbox:] Manage - Credits (Set)") .setDescription(`You must provide a guild.`) .setTimestamp(new Date()) @@ -91,60 +91,52 @@ export default { }); } - // toUser Information - const toUser = await fetchUser(discordUser, guild); + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: discordUser.id, + guildId: guild.id, + }, + }, + update: { creditsEarned: creditAmount }, + create: { + creditsEarned: creditAmount, + user: { + connectOrCreate: { + create: { + id: discordUser.id, + }, + where: { + id: discordUser.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, + }); - // If toUser does not exist - if (toUser === null) { - logger?.silly(`User does not exist`); + logger.silly(createGuildMember); - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Set)") - .setDescription(`The user you provided does not exist.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - // If toUser.credits does not exist - if (toUser?.credits === null) { - logger?.silly(`User does not have any credits`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Set)") - .setDescription(`The user you provided does not have any credits.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - // Set toUser with amount - toUser.credits = creditAmount; - - // Save toUser - await toUser?.save()?.then(async () => { - logger?.silly(`Saved user`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Set)") - .setDescription( - `Set **${discordUser}**'s credits to **${creditAmount}**.` - ) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); + return interaction?.editReply({ + embeds: [ + new EmbedBuilder() + .setTitle("[:toolbox:] Manage - Credits (Set)") + .setDescription( + `Set **${discordUser}**'s credits to **${creditAmount}**.` + ) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], }); }, }; diff --git a/src/plugins/commands/manage/modules/credits/modules/take/index.ts b/src/commands/manage/modules/credits/modules/take/index.ts similarity index 52% rename from src/plugins/commands/manage/modules/credits/modules/take/index.ts rename to src/commands/manage/modules/credits/modules/take/index.ts index fd3076b..9e808af 100644 --- a/src/plugins/commands/manage/modules/credits/modules/take/index.ts +++ b/src/commands/manage/modules/credits/modules/take/index.ts @@ -1,27 +1,23 @@ // Dependencies -import { CommandInteraction, MessageEmbed, Permissions } from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; - -// Handlers -import logger from "../../../../../../../logger"; - -// Helpers -import pluralize from "../../../../../../../helpers/pluralize"; - // Models -import fetchUser from "../../../../../../../helpers/fetchUser"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, +} from "discord.js"; +// Configurations +import getEmbedConfig from "../../../../../../helpers/getEmbedData"; +// Helpers../../../../../../../helpers/userData +import pluralize from "../../../../../../helpers/pluralize"; +// Handlers +import prisma from "../../../../../../handlers/database"; +import deferReply from "../../../../../../handlers/deferReply"; +import checkPermission from "../../../../../../helpers/checkPermission"; +import logger from "../../../../../../middlewares/logger"; // Function export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("take") @@ -39,13 +35,17 @@ export default { .setRequired(true) ); }, - execute: async (interaction: CommandInteraction) => { + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); // Destructure const { guild, options } = interaction; // User option - const optionUser = options?.getUser("user"); + const discordReceiver = options?.getUser("user"); // Amount option const optionAmount = options?.getInteger("amount"); @@ -56,7 +56,7 @@ export default { return interaction?.editReply({ embeds: [ - new MessageEmbed() + new EmbedBuilder() .setTitle("[:toolbox:] Manage - Credits (Take)") .setDescription(`You must provide an amount.`) .setTimestamp(new Date()) @@ -72,7 +72,7 @@ export default { return interaction?.editReply({ embeds: [ - new MessageEmbed() + new EmbedBuilder() .setTitle("[:toolbox:] Manage - Credits (Take)") .setDescription(`You must provide an amount greater than zero.`) .setTimestamp(new Date()) @@ -82,12 +82,12 @@ export default { }); } - if (optionUser === null) { + if (discordReceiver === null) { logger?.silly(`Discord receiver is null`); return interaction?.editReply({ embeds: [ - new MessageEmbed() + new EmbedBuilder() .setTitle("[:toolbox:] Manage - Credits (Take)") .setDescription(`You must provide a user.`) .setTimestamp(new Date()) @@ -101,7 +101,7 @@ export default { return interaction?.editReply({ embeds: [ - new MessageEmbed() + new EmbedBuilder() .setTitle("[:toolbox:] Manage - Credits (Take)") .setDescription(`You must be in a guild.`) .setTimestamp(new Date()) @@ -111,60 +111,52 @@ export default { }); } - // toUser Information - const toUser = await fetchUser(optionUser, guild); - - // If toUser does not exist - if (toUser === null) { - logger?.silly(`ToUser is null`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Take)") - .setDescription(`The user you provided does not exist.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - // If toUser.credits does not exist - if (toUser?.credits === null) { - logger?.silly(`ToUser.credits is null`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Take)") - .setDescription(`The user you provided does not have credits.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - // Withdraw amount from toUser - toUser.credits -= optionAmount; - - // Save toUser - await toUser?.save()?.then(async () => { - logger?.silly(`Saved toUser`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Take)") - .setDescription( - `Took ${pluralize(optionAmount, "credit")} from ${optionUser}.` - ) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: discordReceiver.id, + guildId: guild.id, + }, + }, + update: { creditsEarned: { decrement: optionAmount } }, + create: { + creditsEarned: -optionAmount, + user: { + connectOrCreate: { + create: { + id: discordReceiver.id, + }, + where: { + id: discordReceiver.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, }); + + logger.silly(createGuildMember); + await interaction?.editReply({ + embeds: [ + new EmbedBuilder() + .setTitle("[:toolbox:] Manage - Credits (Take)") + .setDescription( + `Took ${pluralize(optionAmount, "credit")} from ${discordReceiver}.` + ) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + return; }, }; diff --git a/src/commands/manage/modules/credits/modules/transfer/index.ts b/src/commands/manage/modules/credits/modules/transfer/index.ts new file mode 100644 index 0000000..6224ec4 --- /dev/null +++ b/src/commands/manage/modules/credits/modules/transfer/index.ts @@ -0,0 +1,81 @@ +// Dependencies +// Models +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, +} from "discord.js"; +import transferCredits from "../../../../../../helpers/transferCredits"; +// Configurations +import deferReply from "../../../../../../handlers/deferReply"; +import checkPermission from "../../../../../../helpers/checkPermission"; +import getEmbedConfig from "../../../../../../helpers/getEmbedData"; + +// Function +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("transfer") + .setDescription("Transfer credits from one user to another.") + .addUserOption((option) => + option + .setName("from") + .setDescription("The user to transfer credits from.") + .setRequired(true) + ) + .addUserOption((option) => + option + .setName("to") + .setDescription("The user to transfer credits to.") + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName("amount") + .setDescription(`The amount of credits to transfer.`) + .setRequired(true) + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); // Destructure member + const { guild, options } = interaction; + + // Get options + const optionFromUser = options?.getUser("from"); + const optionToUser = options?.getUser("to"); + const optionAmount = options?.getInteger("amount"); + + if (optionAmount === null) throw new Error("Amount is not specified"); + + if (optionAmount <= 0) + throw new Error("You need to set amount above zero to transfer."); + + if (!guild) throw new Error(`We could not find this guild.`); + + if (!optionFromUser) + throw new Error("You must provide a user to transfer from."); + + if (!optionToUser) + throw new Error("You must provide a user to transfer to."); + + await transferCredits(guild, optionFromUser, optionToUser, optionAmount); + + return interaction?.editReply({ + embeds: [ + new EmbedBuilder() + .setTitle("[:toolbox:] Manage - Credits (Transfer)") + .setDescription(`Transferred ${optionAmount} credits.`) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + }, +}; diff --git a/src/commands/moderation/index.ts b/src/commands/moderation/index.ts new file mode 100644 index 0000000..cfc9e20 --- /dev/null +++ b/src/commands/moderation/index.ts @@ -0,0 +1,27 @@ +import { SlashCommandBuilder } from "@discordjs/builders"; +import { ChatInputCommandInteraction } from "discord.js"; + +// Modules +import modulePrune from "./modules/prune"; + +export const builder = new SlashCommandBuilder() + .setName("moderation") + .setDescription("Moderation.") + .setDMPermission(false) + + .addSubcommand(modulePrune.builder); + +// Execute the command +export const execute = async (interaction: ChatInputCommandInteraction) => { + switch (interaction.options.getSubcommand()) { + case "prune": { + await modulePrune.execute(interaction); + break; + } + default: { + throw new Error( + `Unknown subcommand: ${interaction.options.getSubcommand()}` + ); + } + } +}; diff --git a/src/plugins/commands/moderation/modules/prune/index.ts b/src/commands/moderation/modules/prune/index.ts similarity index 53% rename from src/plugins/commands/moderation/modules/prune/index.ts rename to src/commands/moderation/modules/prune/index.ts index c847e47..3641d98 100644 --- a/src/plugins/commands/moderation/modules/prune/index.ts +++ b/src/commands/moderation/modules/prune/index.ts @@ -1,22 +1,18 @@ // Dependencies -import { - CommandInteraction, - Permissions, -} from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { + ChannelType, + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, +} from "discord.js"; +import deferReply from "../../../../handlers/deferReply"; +import checkPermission from "../../../../helpers/checkPermission"; +// Configurations +import getEmbedConfig from "../../../../helpers/getEmbedData"; // Function export default { - metadata: { - guildOnly: true, - ephemeral: false, - permissions: [Permissions.FLAGS.MANAGE_MESSAGES], - }, - builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("prune") @@ -31,33 +27,34 @@ export default { option.setName("bots").setDescription("Include bots.") ); }, - execute: async (interaction: CommandInteraction) => { - const { successColor, footerText, footerIcon } = await getEmbedConfig( + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, false); + + checkPermission(interaction, PermissionsBitField.Flags.ManageMessages); + + const { errorColor, footerText, footerIcon } = await getEmbedConfig( interaction.guild ); const count = interaction.options.getInteger("count"); - if (count == null) return; + if (count === null) return; const bots = interaction.options.getBoolean("bots"); if (count < 1 || count > 100) { - const interactionEmbed = { - title: "[:police_car:] Prune", - description: `You can only prune between 1 and 100 messages.`, - color: successColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }; + const interactionEmbed = new EmbedBuilder() + .setTitle("[:police_car:] Prune") + .setDescription(`You can only prune between 1 and 100 messages.`) + .setTimestamp() + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }); + await interaction.editReply({ embeds: [interactionEmbed], }); return; } - if (interaction?.channel?.type !== "GUILD_TEXT") return; + if (interaction?.channel?.type !== ChannelType.GuildText) return; await interaction.channel.messages.fetch().then(async (messages) => { const messagesToDelete = ( bots @@ -68,20 +65,17 @@ export default { ) ).first(count); - if (interaction?.channel?.type !== "GUILD_TEXT") return; + if (interaction?.channel?.type !== ChannelType.GuildText) return; await interaction.channel .bulkDelete(messagesToDelete, true) .then(async () => { - const interactionEmbed = { - title: "[:police_car:] Prune", - description: `Successfully pruned \`${count}\` messages.`, - color: successColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }; + const interactionEmbed = new EmbedBuilder() + .setTitle("[:police_car:] Prune") + .setDescription(`Successfully pruned \`${count}\` messages.`) + .setTimestamp() + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }); + await interaction.editReply({ embeds: [interactionEmbed], }); diff --git a/src/commands/reputation/index.ts b/src/commands/reputation/index.ts new file mode 100644 index 0000000..a550a7e --- /dev/null +++ b/src/commands/reputation/index.ts @@ -0,0 +1,29 @@ +// Dependencies +import { SlashCommandBuilder } from "@discordjs/builders"; +import { ChatInputCommandInteraction } from "discord.js"; + +// Modules +import moduleGive from "./modules/give"; +import moduleView from "./modules/view"; + +// Function +export const builder = new SlashCommandBuilder() + .setName("reputation") + .setDescription("Manage reputation.") + .setDMPermission(false) + + // Modules + .addSubcommand(moduleGive.builder) + .addSubcommand(moduleView.builder); + +// Execute function +export const execute = async (interaction: ChatInputCommandInteraction) => { + if (interaction.options.getSubcommand() === "give") { + await moduleGive.execute(interaction); + return; + } + if (interaction.options.getSubcommand() === "view") { + await moduleView.execute(interaction); + return; + } +}; diff --git a/src/plugins/commands/reputation/modules/give/components/noSelfReputation.ts b/src/commands/reputation/modules/give/components/noSelfReputation.ts similarity index 68% rename from src/plugins/commands/reputation/modules/give/components/noSelfReputation.ts rename to src/commands/reputation/modules/give/components/noSelfReputation.ts index f6a6778..8e3b913 100644 --- a/src/plugins/commands/reputation/modules/give/components/noSelfReputation.ts +++ b/src/commands/reputation/modules/give/components/noSelfReputation.ts @@ -1,5 +1,6 @@ import { User } from "discord.js"; -export default async (to: User | null, from: User | null) => { + +export default (to: User | null, from: User | null) => { if (from?.id === to?.id) { throw new Error("You cannot give reputation to yourself."); } diff --git a/src/commands/reputation/modules/give/index.ts b/src/commands/reputation/modules/give/index.ts new file mode 100644 index 0000000..7adf7be --- /dev/null +++ b/src/commands/reputation/modules/give/index.ts @@ -0,0 +1,117 @@ +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { ChatInputCommandInteraction, EmbedBuilder } from "discord.js"; +import { command as CooldownCommand } from "../../../../handlers/cooldown"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; +import noSelfReputation from "./components/noSelfReputation"; + +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; + +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("give") + .setDescription("Give reputation to a user") + .addUserOption((option) => + option + .setName("target") + .setDescription("The user you want to repute.") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("type") + .setDescription("What type of reputation you want to repute") + .setRequired(true) + .addChoices( + { name: "Positive", value: "positive" }, + { + name: "Negative", + value: "negative", + } + ) + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + const { options, user, guild } = interaction; + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + guild + ); + + const optionTarget = options?.getUser("target"); + const optionType = options?.getString("type"); + + if (!guild) throw new Error("Guild is undefined"); + if (!optionTarget) throw new Error("Target is not defined"); + + // Pre-checks + noSelfReputation(optionTarget, user); + + // Check if user is on cooldown otherwise create one + await CooldownCommand( + interaction, + parseInt(process.env.REPUTATION_TIMEOUT) + ); + + switch (optionType) { + case "positive": { + const createUser = await prisma.user.upsert({ + where: { + id: optionTarget.id, + }, + update: { + reputationsEarned: { + increment: 1, + }, + }, + create: { + id: optionTarget.id, + reputationsEarned: 1, + }, + }); + + logger.silly(createUser); + break; + } + case "negative": { + const createUser = await prisma.user.upsert({ + where: { + id: optionTarget.id, + }, + update: { + reputationsEarned: { + decrement: 1, + }, + }, + create: { + id: optionTarget.id, + reputationsEarned: -1, + }, + }); + + logger.silly(createUser); + break; + } + default: { + throw new Error("Invalid reputation type"); + } + } + + const interactionEmbed = new EmbedBuilder() + .setTitle("[:loudspeaker:] Give") + .setDescription( + `You have given a ${optionType} repute to ${optionTarget}` + ) + .setTimestamp() + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }); + + await interaction.editReply({ + embeds: [interactionEmbed], + }); + }, +}; diff --git a/src/commands/reputation/modules/view/index.ts b/src/commands/reputation/modules/view/index.ts new file mode 100644 index 0000000..23598a0 --- /dev/null +++ b/src/commands/reputation/modules/view/index.ts @@ -0,0 +1,85 @@ +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { ChatInputCommandInteraction, EmbedBuilder } from "discord.js"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; + +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("view") + .setDescription("View a user's reputation value") + .addUserOption((option) => + option + .setName("target") + .setDescription("The user you want to check.") + .setRequired(true) + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + const { options, guild } = interaction; + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + guild + ); + + const optionTarget = options?.getUser("target"); + + if (!guild) throw new Error("Guild is undefined"); + if (!optionTarget) throw new Error("Target is not defined"); + + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: optionTarget.id, + guildId: guild.id, + }, + }, + update: {}, + create: { + user: { + connectOrCreate: { + create: { + id: optionTarget.id, + }, + where: { + id: optionTarget.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, + include: { + user: true, + guild: true, + }, + }); + + logger.silly(createGuildMember); + + const interactionEmbed = new EmbedBuilder() + .setTitle("[:loudspeaker:] View") + .setDescription( + `${optionTarget} has ${createGuildMember.user.reputationsEarned}.` + ) + .setTimestamp() + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }); + + await interaction.editReply({ + embeds: [interactionEmbed], + }); + }, +}; diff --git a/src/commands/shop/index.ts b/src/commands/shop/index.ts new file mode 100644 index 0000000..bf3c20d --- /dev/null +++ b/src/commands/shop/index.ts @@ -0,0 +1,42 @@ +// Dependencies +import { SlashCommandBuilder } from "@discordjs/builders"; +import { ChatInputCommandInteraction } from "discord.js"; + +// Modules +import moduleCpgg from "./modules/cpgg"; +import moduleRoles from "./modules/roles"; + +// Function +export const builder = new SlashCommandBuilder() + .setName("shop") + .setDescription("Shop for credits and custom roles.") + .setDMPermission(false) + + // Modules + .addSubcommand(moduleCpgg.builder) + .addSubcommandGroup(moduleRoles.builder); + +// Execute the command +export const execute = async (interaction: ChatInputCommandInteraction) => { + const { options } = interaction; + + switch (options.getSubcommand()) { + case "cpgg": { + await moduleCpgg.execute(interaction); + break; + } + default: { + throw new Error("Could not find module for that command."); + } + } + + switch (options.getSubcommandGroup()) { + case "roles": { + await moduleRoles.execute(interaction); + break; + } + default: { + throw new Error("Could not find module for that command."); + } + } +}; diff --git a/src/commands/shop/modules/cpgg/index.ts b/src/commands/shop/modules/cpgg/index.ts new file mode 100644 index 0000000..1e50907 --- /dev/null +++ b/src/commands/shop/modules/cpgg/index.ts @@ -0,0 +1,195 @@ +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import axios from "axios"; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + ChatInputCommandInteraction, + EmbedBuilder, + Message, +} from "discord.js"; +import { v4 as uuidv4 } from "uuid"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import encryption from "../../../../helpers/encryption"; +import getEmbedData from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; + +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("cpgg") + .setDescription("Buy cpgg power.") + .addIntegerOption((option) => + option + .setName("amount") + .setDescription("How much credits you want to withdraw.") + .setRequired(true) + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedData(interaction.guild); + const { options, guild, user, client } = interaction; + const optionAmount = options?.getInteger("amount"); + if (optionAmount === null) { + logger?.silly(`Amount is null.`); + const interactionEmbed = new EmbedBuilder() + .setTitle("[:dollar:] Gift") + .setDescription("We could not read your requested amount.") + .setTimestamp() + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }); + return interaction?.editReply({ + embeds: [interactionEmbed], + }); + } + if (!guild) throw new Error("Guild not found"); + + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: user.id, + guildId: guild.id, + }, + }, + update: {}, + create: { + user: { + connectOrCreate: { + create: { + id: user.id, + }, + where: { + id: user.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, + include: { + user: true, + guild: true, + }, + }); + + logger.silly(createGuildMember); + + const dmUser = client?.users?.cache?.get(user?.id); + + if ((optionAmount || createGuildMember.creditsEarned) < 100) + throw new Error("You can't withdraw to CPGG below 100 credits."); + + if ((optionAmount || createGuildMember.creditsEarned) > 1000000) + throw new Error("Amount or user credits is above 1.000.000."); + + if (createGuildMember.creditsEarned < optionAmount) + throw new Error("You can't withdraw more than you have on your account."); + + if ( + !createGuildMember.guild.apiCpggUrlIv || + !createGuildMember.guild.apiCpggUrlContent + ) + throw new Error("No API url available"); + + if ( + !createGuildMember.guild.apiCpggTokenIv || + !createGuildMember.guild.apiCpggTokenContent + ) + throw new Error("No API token available"); + + const code = uuidv4(); + const url = encryption.decrypt({ + iv: createGuildMember.guild.apiCpggUrlIv, + content: createGuildMember.guild.apiCpggUrlContent, + }); + const api = axios?.create({ + baseURL: `${url}/api/`, + headers: { + Authorization: `Bearer ${encryption.decrypt({ + iv: createGuildMember.guild.apiCpggTokenIv, + content: createGuildMember.guild.apiCpggTokenContent, + })}`, + }, + }); + const shopUrl = `${url}/store`; + const buttons = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel("Redeem it here") + .setStyle(ButtonStyle.Link) + .setEmoji("🏦") + .setURL(`${shopUrl}?voucher=${code}`) + ); + await api + ?.post("vouchers", { + uses: 1, + code, + credits: optionAmount || createGuildMember.creditsEarned, + memo: `${interaction?.createdTimestamp} - ${interaction?.user?.id}`, + }) + ?.then(async () => { + logger?.silly(`Successfully created voucher.`); + createGuildMember.creditsEarned -= + optionAmount || createGuildMember.creditsEarned; + + const updateGuildMember = await prisma.guildMember.update({ + where: { + userId_guildId: { + userId: user.id, + guildId: guild.id, + }, + }, + data: { + creditsEarned: { + decrement: optionAmount || createGuildMember.creditsEarned, + }, + }, + }); + + logger.silly(updateGuildMember); + + if (!interaction.guild) throw new Error("Guild is undefined"); + const dmEmbed = new EmbedBuilder() + .setTitle("[:shopping_cart:] CPGG") + .setDescription( + `This voucher comes from **${interaction.guild.name}**.` + ) + .setTimestamp() + .addFields({ + name: "💶 Credits", + value: `${optionAmount || createGuildMember.creditsEarned}`, + inline: true, + }) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }); + await dmUser + ?.send({ + embeds: [dmEmbed], + components: [buttons], + }) + .then(async (msg: Message) => { + const interactionEmbed = new EmbedBuilder() + .setTitle("[:shopping_cart:] CPGG") + .setDescription(`I have sent you the code in [DM](${msg.url})!`) + .setTimestamp() + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }); + await interaction?.editReply({ + embeds: [interactionEmbed], + }); + return; + }); + }); + }, +}; diff --git a/src/commands/shop/modules/roles/index.ts b/src/commands/shop/modules/roles/index.ts new file mode 100644 index 0000000..4d954cf --- /dev/null +++ b/src/commands/shop/modules/roles/index.ts @@ -0,0 +1,45 @@ +// Dependencies +import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; +import { ChatInputCommandInteraction } from "discord.js"; + +// Handlers + +// Modules +import moduleBuy from "./modules/buy"; +import moduleCancel from "./modules/cancel"; + +import prisma from "../../../../handlers/database"; + +export default { + builder: (group: SlashCommandSubcommandGroupBuilder) => { + return ( + group + .setName("roles") + .setDescription("Shop for custom roles.") + + // Modules + .addSubcommand(moduleBuy.builder) + .addSubcommand(moduleCancel.builder) + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + if (!interaction.guild) return; + const { options, guild } = interaction; + + const getGuild = await prisma.guild.findUnique({ + where: { id: guild.id }, + }); + if (!getGuild) throw new Error("Guild not found"); + + if (!getGuild.shopRolesEnabled) + throw new Error("This server has disabled shop roles."); + + if (options?.getSubcommand() === "buy") { + await moduleBuy.execute(interaction); + } + + if (options?.getSubcommand() === "cancel") { + await moduleCancel.execute(interaction); + } + }, +}; diff --git a/src/commands/shop/modules/roles/modules/buy/index.ts b/src/commands/shop/modules/roles/modules/buy/index.ts new file mode 100644 index 0000000..a6e3b0b --- /dev/null +++ b/src/commands/shop/modules/roles/modules/buy/index.ts @@ -0,0 +1,178 @@ +// Dependencies +// Helpers +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { + ChatInputCommandInteraction, + ColorResolvable, + EmbedBuilder, + GuildMemberRoleManager, +} from "discord.js"; +import deferReply from "../../../../../../handlers/deferReply"; +import getEmbedData from "../../../../../../helpers/getEmbedData"; +import logger from "../../../../../../middlewares/logger"; +// Configurations +// import fetchUser from "../../../../../../helpers/userData"; +// Models + +import prisma from "../../../../../../handlers/database"; +import pluralize from "../../../../../../helpers/pluralize"; + +// Function +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("buy") + .setDescription("Buy a custom role.") + .addStringOption((option) => + option + .setName("name") + .setDescription("Name of the role you wish to buy.") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("color") + .setDescription("Color of the role you wish to buy.") + .setRequired(true) + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + const { successColor, footerText, footerIcon } = await getEmbedData( + interaction.guild + ); + const { options, guild, user, member } = interaction; + const optionName = options?.getString("name"); + const optionColor = options?.getString("color"); + // If amount is null + if (optionName === null) + throw new Error("We could not read your requested name"); + await guild?.roles + .create({ + name: optionName, + color: optionColor as ColorResolvable, + reason: `${user?.id} bought from shop`, + }) + .then(async (role) => { + const userId = "SNOWFLKAE"; + const guildId = "SNOWFLAKE"; + + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId, + guildId, + }, + }, + update: {}, + create: { + user: { + connectOrCreate: { + create: { + id: userId, + }, + where: { + id: userId, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guildId, + }, + where: { + id: guildId, + }, + }, + }, + }, + include: { + user: true, + guild: true, + }, + }); + + logger.silly(createGuildMember); + + // Get guild object + const pricePerHour = createGuildMember.guild.shopRolesPricePerHour; + + const updateGuildMember = await prisma.guildMember.update({ + where: { + userId_guildId: { + userId, + guildId, + }, + }, + data: { + creditsEarned: { decrement: pricePerHour }, + }, + }); + + logger.silly(updateGuildMember); + + const createShopRole = await prisma.guildShopRoles.upsert({ + where: { + guildId_userId_roleId: { + guildId: guild.id, + userId: user.id, + roleId: role.id, + }, + }, + update: {}, + create: { + roleId: role.id, + lastPayed: new Date(), + user: { + connectOrCreate: { + create: { + id: user.id, + }, + where: { + id: user.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, + include: { + user: true, + guild: true, + }, + }); + + logger.silly(createShopRole); + + await (member?.roles as GuildMemberRoleManager)?.add(role?.id); + logger?.silly(`Role ${role?.name} was bought by ${user?.tag}`); + const interactionEmbed = new EmbedBuilder() + .setTitle("[:shopping_cart:] Buy") + .setDescription( + `You bought **${optionName}** for **${pluralize( + pricePerHour, + "credit" + )}**.` + ) + .setTimestamp() + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }); + return interaction?.editReply({ + embeds: [interactionEmbed], + }); + }) + .catch(() => { + throw new Error("Failed creating role."); + }); + }, +}; diff --git a/src/commands/shop/modules/roles/modules/cancel/index.ts b/src/commands/shop/modules/roles/modules/cancel/index.ts new file mode 100644 index 0000000..8807ac4 --- /dev/null +++ b/src/commands/shop/modules/roles/modules/cancel/index.ts @@ -0,0 +1,127 @@ +// Dependencies +// Helpers +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { + ChatInputCommandInteraction, + EmbedBuilder, + GuildMemberRoleManager, +} from "discord.js"; +// Configurations +// Models +import deferReply from "../../../../../../handlers/deferReply"; +import logger from "../../../../../../middlewares/logger"; +// Configurations +// Models + +import prisma from "../../../../../../handlers/database"; +import getEmbedData from "../../../../../../helpers/getEmbedData"; +import pluralize from "../../../../../../helpers/pluralize"; + +// Function +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("cancel") + .setDescription("Cancel a purchase.") + .addRoleOption((option) => + option + .setName("role") + .setDescription("Role you wish to cancel.") + .setRequired(true) + ); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + const { successColor, footerText, footerIcon } = await getEmbedData( + interaction.guild + ); + const { options, guild, user, member } = interaction; + const optionRole = options.getRole("role"); + if (optionRole === null) + throw new Error("We could not read your requested role."); + if (!guild) throw new Error("No guild specified"); + if (!user) throw new Error("No user specified"); + + const roleExist = await prisma.guildShopRoles.findUnique({ + where: { + guildId_userId_roleId: { + guildId: guild.id, + userId: user.id, + roleId: optionRole.id, + }, + }, + }); + if (roleExist === null) return; + await (member?.roles as GuildMemberRoleManager)?.remove(optionRole?.id); + await guild?.roles + .delete(optionRole?.id, `${user?.id} canceled from shop`) + .then(async () => { + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: user.id, + guildId: guild.id, + }, + }, + update: {}, + create: { + user: { + connectOrCreate: { + create: { + id: user.id, + }, + where: { + id: user.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, + include: { + user: true, + guild: true, + }, + }); + + logger.silly(createGuildMember); + + if (!createGuildMember) throw new Error("Guild member not created"); + + const deleteShopRole = await prisma.guildShopRoles.delete({ + where: { + guildId_userId_roleId: { + guildId: guild?.id, + userId: user?.id, + roleId: optionRole?.id, + }, + }, + }); + + logger.silly(deleteShopRole); + + const interactionEmbed = new EmbedBuilder() + .setTitle("[:shopping_cart:] Cancel") + .setDescription(`You have canceled ${optionRole.name}.`) + .setTimestamp() + .setColor(successColor) + .addFields({ + name: "Your balance", + value: `${pluralize(createGuildMember.creditsEarned, "credit")}`, + }) + .setFooter({ text: footerText, iconURL: footerIcon }); + return interaction?.editReply({ + embeds: [interactionEmbed], + }); + }); + }, +}; diff --git a/src/commands/utility/index.ts b/src/commands/utility/index.ts new file mode 100644 index 0000000..a58d229 --- /dev/null +++ b/src/commands/utility/index.ts @@ -0,0 +1,40 @@ +import { SlashCommandBuilder } from "@discordjs/builders"; +import { ChatInputCommandInteraction } from "discord.js"; + +// Modules +import moduleAbout from "./modules/about"; +import moduleAvatar from "./modules/avatar"; +import modulePing from "./modules/ping"; +import moduleStats from "./modules/stats"; + +export const builder = new SlashCommandBuilder() + .setName("utility") + .setDescription("Common utility.") + + // Modules + .addSubcommand(moduleAbout.builder) + .addSubcommand(moduleStats.builder) + .addSubcommand(moduleAvatar.builder) + .addSubcommand(modulePing.builder); + +// Execute the command +export const execute = async (interaction: ChatInputCommandInteraction) => { + switch (interaction.options.getSubcommand()) { + case "about": + await moduleAbout.execute(interaction); + break; + case "stats": + await moduleStats.execute(interaction); + break; + case "avatar": + await moduleAvatar.execute(interaction); + break; + case "ping": + await modulePing.execute(interaction); + break; + default: + throw new Error( + `Unknown subcommand: ${interaction.options.getSubcommand()}` + ); + } +}; diff --git a/src/commands/utility/modules/about/index.ts b/src/commands/utility/modules/about/index.ts new file mode 100644 index 0000000..01f0ad7 --- /dev/null +++ b/src/commands/utility/modules/about/index.ts @@ -0,0 +1,75 @@ +// Dependencies +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + CommandInteraction, + EmbedBuilder, +} from "discord.js"; +import deferReply from "../../../../handlers/deferReply"; +// Configurations +import getEmbedConfig from "../../../../helpers/getEmbedData"; + +// Function +export default { + builder: (command: SlashCommandSubcommandBuilder) => { + return command.setName("about").setDescription("About this bot!)"); + }, + execute: async (interaction: CommandInteraction) => { + await deferReply(interaction, false); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + + const buttons = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel("Source Code") + .setStyle(ButtonStyle.Link) + .setEmoji("📄") + .setURL("https://github.com/ZynerOrg/xyter"), + new ButtonBuilder() + .setLabel("Documentation") + .setStyle(ButtonStyle.Link) + .setEmoji("📚") + .setURL("https://xyter.zyner.org"), + new ButtonBuilder() + .setLabel("Website") + .setStyle(ButtonStyle.Link) + .setEmoji("🌐") + .setURL("https://zyner.org"), + new ButtonBuilder() + .setLabel("Get Help") + .setStyle(ButtonStyle.Link) + .setEmoji("💬") + .setURL("https://discord.zyner.org"), + new ButtonBuilder() + .setLabel(`Hosted by ${process.env.BOT_HOSTER_NAME}`) + .setStyle(ButtonStyle.Link) + .setEmoji("⚒️") + .setURL(`${process.env.BOT_HOSTER_URL}`) + ); + + const interactionEmbed = new EmbedBuilder() + .setColor(successColor) + .setTitle("[:tools:] About") + .setDescription( + ` +**Xyter**'s goal is to provide a __privacy-friendly__ discord bot. +We created **Xyter** to **replace the mess** of having a dozen or so bots in __your__ community. +On top of this, you can also see our **source code** for **security** and **privacy** issues. +As well as making your own **fork** of the bot, you can also get **help** from our community. + +Developed with ❤️ by **Zyner**, a non-profit project by teens. + ` + ) + .setTimestamp() + .setFooter({ text: footerText, iconURL: footerIcon }); + + await interaction.editReply({ + embeds: [interactionEmbed], + components: [buttons], + }); + }, +}; diff --git a/src/plugins/commands/utility/modules/avatar/index.ts b/src/commands/utility/modules/avatar/index.ts similarity index 79% rename from src/plugins/commands/utility/modules/avatar/index.ts rename to src/commands/utility/modules/avatar/index.ts index 24ab6c1..a6e4ffb 100644 --- a/src/plugins/commands/utility/modules/avatar/index.ts +++ b/src/commands/utility/modules/avatar/index.ts @@ -1,11 +1,9 @@ -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - -import { CommandInteraction, MessageEmbed } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { CommandInteraction, EmbedBuilder } from "discord.js"; +import deferReply from "../../../../handlers/deferReply"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; export default { - metadata: { guildOnly: false, ephemeral: false }, - builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("avatar") @@ -17,6 +15,8 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { + await deferReply(interaction, false); + const { successColor, footerText, footerIcon } = await getEmbedConfig( interaction.guild ); @@ -24,7 +24,7 @@ export default { const targetUser = userOption || interaction.user; - const embed = new MessageEmbed() + const embed = new EmbedBuilder() .setTitle("[:tools:] Avatar") .setTimestamp(new Date()) .setFooter({ text: footerText, iconURL: footerIcon }); diff --git a/src/plugins/commands/utility/modules/ping/index.ts b/src/commands/utility/modules/ping/index.ts similarity index 63% rename from src/plugins/commands/utility/modules/ping/index.ts rename to src/commands/utility/modules/ping/index.ts index 420329e..3855e89 100644 --- a/src/plugins/commands/utility/modules/ping/index.ts +++ b/src/commands/utility/modules/ping/index.ts @@ -1,26 +1,25 @@ // Dependencies -import { CommandInteraction } from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { CommandInteraction, EmbedBuilder } from "discord.js"; +// Configurations +import deferReply from "../../../../handlers/deferReply"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; // Function export default { - metadata: { guildOnly: false, ephemeral: false }, - builder: (command: SlashCommandSubcommandBuilder) => { return command.setName("ping").setDescription("Ping this bot"); }, execute: async (interaction: CommandInteraction) => { + await deferReply(interaction, false); + const { successColor, footerText, footerIcon } = await getEmbedConfig( interaction.guild ); - const interactionEmbed = { - title: "[:tools:] Ping", - fields: [ + const interactionEmbed = new EmbedBuilder() + .setTitle("[:tools:] Ping") + .addFields( { name: "📦 Deliver Latency", value: `${Math.abs(Date.now() - interaction.createdTimestamp)} ms`, @@ -30,15 +29,12 @@ export default { name: "🤖 API Latency", value: `${Math.round(interaction.client.ws.ping)} ms`, inline: true, - }, - ], - color: successColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }; + } + ) + .setTimestamp() + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }); + await interaction.editReply({ embeds: [interactionEmbed], }); diff --git a/src/plugins/commands/utility/modules/stats/index.ts b/src/commands/utility/modules/stats/index.ts similarity index 75% rename from src/plugins/commands/utility/modules/stats/index.ts rename to src/commands/utility/modules/stats/index.ts index a2a8282..b422423 100644 --- a/src/plugins/commands/utility/modules/stats/index.ts +++ b/src/commands/utility/modules/stats/index.ts @@ -1,17 +1,19 @@ -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; -export default { - metadata: { guildOnly: false, ephemeral: false }, +import { CommandInteraction, EmbedBuilder } from "discord.js"; +import deferReply from "../../../../handlers/deferReply"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +export default { builder: (command: SlashCommandSubcommandBuilder) => { return command.setName("stats").setDescription("Check bot statistics!)"); }, execute: async (interaction: CommandInteraction) => { + await deferReply(interaction, false); + const { successColor, footerText, footerIcon } = await getEmbedConfig( interaction.guild ); + const { client } = interaction; if (client?.uptime === null) return; let totalSeconds = client?.uptime / 1000; @@ -24,10 +26,12 @@ export default { const uptime = `${days} days, ${hours} hours, ${minutes} minutes and ${seconds} seconds`; - const interactionEmbed = { - title: ":hammer: Utilities - Stats", - description: "Below you can see a list of statistics about the bot.", - fields: [ + const interactionEmbed = new EmbedBuilder() + .setColor(successColor) + .setTitle("[:hammer:] Stats") + .setDescription("Below you can see a list of statistics about the bot.") + .setTimestamp() + .addFields( { name: "⏰ Latency", value: `${Date?.now() - interaction?.createdTimestamp} ms`, @@ -55,15 +59,10 @@ export default { 0 )}`, inline: true, - }, - ], - color: successColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }; + } + ) + .setFooter({ text: footerText, iconURL: footerIcon }); + interaction?.editReply({ embeds: [interactionEmbed] }); }, }; diff --git a/src/config_example/database.ts b/src/config_example/database.ts deleted file mode 100644 index 35e2e27..0000000 --- a/src/config_example/database.ts +++ /dev/null @@ -1,3 +0,0 @@ -// MongoDB connection string -export const url = - "mongodb+srv://username:password@server/database?retryWrites=true&w=majority"; diff --git a/src/config_example/discord.ts b/src/config_example/discord.ts deleted file mode 100644 index 7b22c28..0000000 --- a/src/config_example/discord.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Intents } from "discord.js"; // discord.js - -// Discord API token -export const token = ""; - -// Discord API id -export const clientId = ""; - -// Discord API intents -export const intents = [ - Intents.FLAGS.GUILDS, - Intents.FLAGS.GUILD_MESSAGES, - Intents.FLAGS.GUILD_MEMBERS, -]; diff --git a/src/config_example/embed.ts b/src/config_example/embed.ts deleted file mode 100644 index d6eebb1..0000000 --- a/src/config_example/embed.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Dependencies -import { ColorResolvable } from "discord.js"; - -// Color for successfully actions -export const successColor: ColorResolvable = "#22bb33"; - -// Color for waiting actions -export const waitColor: ColorResolvable = "#f0ad4e"; - -// Color for error actions -export const errorColor: ColorResolvable = "#bb2124"; - -// Footer text -export const footerText = "https://github.com/ZynerOrg/xyter"; - -// Footer icon -export const footerIcon = "https://github.com/ZynerOrg.png"; diff --git a/src/config_example/encryption.ts b/src/config_example/encryption.ts deleted file mode 100644 index 9d547f1..0000000 --- a/src/config_example/encryption.ts +++ /dev/null @@ -1,5 +0,0 @@ -// Encryption algorithm -export const algorithm = "aes-256-ctr"; - -// Encryption secret (strictly 32 length) -export const secretKey = ""; diff --git a/src/config_example/other.ts b/src/config_example/other.ts deleted file mode 100644 index eefd62c..0000000 --- a/src/config_example/other.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Development features -export const devMode = false; - -// Development guild -export const guildId = ""; - -// Hoster name -export const hosterName = "someone"; - -// Hoster Url -export const hosterUrl = "https://xyter.zyner.org/customization/change-hoster"; - -// Winston log level -export const logLevel = "info"; diff --git a/src/config_example/reputation.ts b/src/config_example/reputation.ts deleted file mode 100644 index 064edc9..0000000 --- a/src/config_example/reputation.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Timeout between repute someone (seconds) -export const timeout = 86400; // One day diff --git a/src/events/guildCreate/index.ts b/src/events/guildCreate/index.ts new file mode 100644 index 0000000..42653b0 --- /dev/null +++ b/src/events/guildCreate/index.ts @@ -0,0 +1,51 @@ +import { Guild } from "discord.js"; +import prisma from "../../handlers/database"; +import updatePresence from "../../handlers/updatePresence"; +import { IEventOptions } from "../../interfaces/EventOptions"; +import logger from "../../middlewares/logger"; + +export const options: IEventOptions = { + type: "on", +}; + +// Execute the function +export const execute = async (guild: Guild) => { + const { client } = guild; + + updatePresence(client); + + // Create guildMember object + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: guild.ownerId, + guildId: guild.id, + }, + }, + update: {}, + create: { + user: { + connectOrCreate: { + create: { + id: guild.ownerId, + }, + where: { + id: guild.ownerId, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, + }); + + logger.silly(createGuildMember); +}; diff --git a/src/events/guildDelete/index.ts b/src/events/guildDelete/index.ts new file mode 100644 index 0000000..3bac8bd --- /dev/null +++ b/src/events/guildDelete/index.ts @@ -0,0 +1,37 @@ +// 3rd party dependencies +import { Guild } from "discord.js"; +import prisma from "../../handlers/database"; +import updatePresence from "../../handlers/updatePresence"; +import { IEventOptions } from "../../interfaces/EventOptions"; +import logger from "../../middlewares/logger"; + +export const options: IEventOptions = { + type: "on", +}; + +// Execute the function +export const execute = async (guild: Guild) => { + const { client } = guild; + + updatePresence(client); + + // Delete guildMember objects + const deleteGuildMembers = prisma.guildMember.deleteMany({ + where: { + guildId: guild.id, + }, + }); + + // Delete guild object + const deleteGuild = prisma.guild.deleteMany({ + where: { + id: guild.id, + }, + }); + + // The transaction runs synchronously so deleteUsers must run last. + await prisma.$transaction([deleteGuildMembers, deleteGuild]); + + logger.silly(deleteGuildMembers); + logger.silly(deleteGuild); +}; diff --git a/src/plugins/events/guildMemberAdd/audits.ts b/src/events/guildMemberAdd/audits.ts similarity index 55% rename from src/plugins/events/guildMemberAdd/audits.ts rename to src/events/guildMemberAdd/audits.ts index 9f94282..0d61fa1 100644 --- a/src/plugins/events/guildMemberAdd/audits.ts +++ b/src/events/guildMemberAdd/audits.ts @@ -1,31 +1,32 @@ -import logger from "../../../logger"; -import { GuildMember, MessageEmbed } from "discord.js"; - -import guildSchema from "../../../models/guild"; - -import getEmbedConfig from "../../../helpers/getEmbedConfig"; +import { ChannelType, EmbedBuilder, GuildMember } from "discord.js"; +import prisma from "../../handlers/database"; +import getEmbedConfig from "../../helpers/getEmbedData"; +import logger from "../../middlewares/logger"; export default { execute: async (member: GuildMember) => { const { client, guild } = member; - const guildData = await guildSchema.findOne({ guildId: member.guild.id }); - if (!guildData) { - throw new Error("Could not find guild"); - } - if (guildData.audits.status !== true) return; - if (!guildData.audits.channelId) { + const getGuild = await prisma.guild.findUnique({ + where: { id: member.guild.id }, + }); + if (!getGuild) throw new Error("Guild not found"); + + if (getGuild.auditsEnabled !== true) return; + if (!getGuild.auditsChannelId) { throw new Error("Channel not found"); } const embedConfig = await getEmbedConfig(guild); - const channel = client.channels.cache.get(guildData.audits.channelId); - if (channel?.type !== "GUILD_TEXT") { + const channel = client.channels.cache.get(getGuild.auditsChannelId); + + if (!channel) throw new Error("Channel not found"); + if (channel.type !== ChannelType.GuildText) { throw new Error("Channel must be a text channel"); } - const embed = new MessageEmbed() + const embed = new EmbedBuilder() .setTimestamp(new Date()) .setAuthor({ name: "Member Joined", @@ -50,12 +51,10 @@ export default { ]), ], }) - .then(async () => { - logger.debug( - `Audit log sent for event guildMemberAdd in guild ${member.guild.name} (${member.guild.id})` - ); + .then(() => { + logger.debug(`Audit log sent for event guildMemberAdd`); }) - .catch(async () => { + .catch(() => { throw new Error("Audit log failed to send"); }); }, diff --git a/src/events/guildMemberAdd/index.ts b/src/events/guildMemberAdd/index.ts new file mode 100644 index 0000000..fc71e62 --- /dev/null +++ b/src/events/guildMemberAdd/index.ts @@ -0,0 +1,60 @@ +// 3rd party dependencies +import { GuildMember } from "discord.js"; +import prisma from "../../handlers/database"; +import updatePresence from "../../handlers/updatePresence"; +import { IEventOptions } from "../../interfaces/EventOptions"; +import logger from "../../middlewares/logger"; +import audits from "./audits"; +import joinMessage from "./joinMessage"; + +export const options: IEventOptions = { + type: "on", +}; + +// Execute the function +export const execute = async (member: GuildMember) => { + const { client, user, guild } = member; + + logger.silly( + `New member: ${user.tag} (${user.id}) added to guild: ${guild.name} (${guild.id})` + ); + + await audits.execute(member); + await joinMessage.execute(member); + updatePresence(client); + + // Create guildMember object + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: user.id, + guildId: guild.id, + }, + }, + update: {}, + create: { + user: { + connectOrCreate: { + create: { + id: user.id, + }, + where: { + id: user.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, + }); + + logger.silly(createGuildMember); +}; diff --git a/src/events/guildMemberAdd/joinMessage.ts b/src/events/guildMemberAdd/joinMessage.ts new file mode 100644 index 0000000..57703fb --- /dev/null +++ b/src/events/guildMemberAdd/joinMessage.ts @@ -0,0 +1,48 @@ +import { ChannelType, EmbedBuilder, GuildMember } from "discord.js"; +import prisma from "../../handlers/database"; +import getEmbedConfig from "../../helpers/getEmbedData"; + +export default { + execute: async (member: GuildMember) => { + const { footerText, footerIcon, successColor } = await getEmbedConfig( + member.guild + ); + + const getGuild = await prisma.guild.findUnique({ + where: { id: member.guild.id }, + }); + + if (!getGuild) throw new Error("Guild not found"); + + const { client } = member; + + if (getGuild.welcomeEnabled !== true) return; + if (!getGuild.welcomeJoinChannelId) return; + + const channel = client.channels.cache.get( + `${getGuild.welcomeJoinChannelId}` + ); + + if (!channel) throw new Error("Channel not found"); + if (channel.type !== ChannelType.GuildText) + throw new Error("Channel is not a text channel"); + + channel.send({ + embeds: [ + new EmbedBuilder() + .setColor(successColor) + .setTitle(`${member.user.username} has joined the server!`) + .setThumbnail(member.user.displayAvatarURL()) + .setDescription( + getGuild.welcomeJoinChannelMessage || + "Configure a join message in the `/settings guild welcome`." + ) + .setTimestamp() + .setFooter({ + text: footerText, + iconURL: footerIcon, + }), + ], + }); + }, +}; diff --git a/src/plugins/events/guildMemberRemove/audits.ts b/src/events/guildMemberRemove/audits.ts similarity index 54% rename from src/plugins/events/guildMemberRemove/audits.ts rename to src/events/guildMemberRemove/audits.ts index 5ce4849..ff4bb59 100644 --- a/src/plugins/events/guildMemberRemove/audits.ts +++ b/src/events/guildMemberRemove/audits.ts @@ -1,31 +1,30 @@ -import logger from "../../../logger"; -import { GuildMember, MessageEmbed } from "discord.js"; - -import guildSchema from "../../../models/guild"; - -import getEmbedConfig from "../../../helpers/getEmbedConfig"; +import { ChannelType, EmbedBuilder, GuildMember } from "discord.js"; +import prisma from "../../handlers/database"; +import getEmbedConfig from "../../helpers/getEmbedData"; +import logger from "../../middlewares/logger"; export default { execute: async (member: GuildMember) => { const { client, guild } = member; - const guildData = await guildSchema.findOne({ guildId: member.guild.id }); - if (!guildData) { - throw new Error("Could not find guild"); - } - if (guildData.audits.status !== true) return; - if (!guildData.audits.channelId) { + const getGuild = await prisma.guild.findUnique({ + where: { id: member.guild.id }, + }); + if (!getGuild) throw new Error("Guild not found"); + + if (getGuild.auditsEnabled !== true) return; + if (!getGuild.auditsChannelId) { throw new Error("Channel not found"); } const embedConfig = await getEmbedConfig(guild); - const channel = client.channels.cache.get(guildData.audits.channelId); - if (channel?.type !== "GUILD_TEXT") { + const channel = client.channels.cache.get(getGuild.auditsChannelId); + if (channel?.type !== ChannelType.GuildText) { throw new Error("Channel must be a text channel"); } - const embed = new MessageEmbed() + const embed = new EmbedBuilder() .setTimestamp(new Date()) .setAuthor({ name: "Member Left", @@ -50,12 +49,10 @@ export default { ]), ], }) - .then(async () => { - logger.debug( - `Audit log sent for event guildMemberRemove in guild ${member.guild.name} (${member.guild.id})` - ); + .then(() => { + logger.debug(`Audit log sent for event guildMemberRemove.`); }) - .catch(async () => { + .catch(() => { throw new Error("Audit log failed to send"); }); }, diff --git a/src/plugins/events/guildMemberRemove/index.ts b/src/events/guildMemberRemove/index.ts similarity index 50% rename from src/plugins/events/guildMemberRemove/index.ts rename to src/events/guildMemberRemove/index.ts index b900a12..bbe61ad 100644 --- a/src/plugins/events/guildMemberRemove/index.ts +++ b/src/events/guildMemberRemove/index.ts @@ -1,18 +1,17 @@ // 3rd party dependencies import { GuildMember } from "discord.js"; - -// Dependencies -import updatePresence from "../../../helpers/updatePresence"; -import dropUser from "../../../helpers/dropUser"; -import logger from "../../../logger"; -import leaveMessage from "./leaveMessage"; +import prisma from "../../handlers/database"; +import updatePresence from "../../handlers/updatePresence"; +import { IEventOptions } from "../../interfaces/EventOptions"; +import logger from "../../middlewares/logger"; import audits from "./audits"; -import { IEventOptions } from "../../../interfaces/EventOptions"; +import leaveMessage from "./leaveMessage"; export const options: IEventOptions = { type: "on", }; +// Execute the function export const execute = async (member: GuildMember) => { const { client, user, guild } = member; @@ -22,6 +21,15 @@ export const execute = async (member: GuildMember) => { await audits.execute(member); await leaveMessage.execute(member); - await dropUser(user, guild); - await updatePresence(client); + updatePresence(client); + + // Delete guildMember object + const deleteGuildMember = await prisma.guildMember.deleteMany({ + where: { + userId: user.id, + guildId: guild.id, + }, + }); + + logger.silly(deleteGuildMember); }; diff --git a/src/events/guildMemberRemove/leaveMessage.ts b/src/events/guildMemberRemove/leaveMessage.ts new file mode 100644 index 0000000..9ac7c85 --- /dev/null +++ b/src/events/guildMemberRemove/leaveMessage.ts @@ -0,0 +1,48 @@ +import { ChannelType, EmbedBuilder, GuildMember } from "discord.js"; +import prisma from "../../handlers/database"; +import getEmbedConfig from "../../helpers/getEmbedData"; + +export default { + execute: async (member: GuildMember) => { + const { footerText, footerIcon, errorColor } = await getEmbedConfig( + member.guild + ); + + const getGuild = await prisma.guild.findUnique({ + where: { id: member.guild.id }, + }); + + if (!getGuild) throw new Error("Guild not found"); + + const { client } = member; + + if (getGuild.welcomeEnabled !== true) return; + if (!getGuild.welcomeLeaveChannelId) return; + + const channel = client.channels.cache.get( + `${getGuild.welcomeLeaveChannelId}` + ); + + if (!channel) throw new Error("Channel not found"); + if (channel.type !== ChannelType.GuildText) + throw new Error("Channel is not a text channel"); + + channel.send({ + embeds: [ + new EmbedBuilder() + .setColor(errorColor) + .setTitle(`${member.user.username} has left the server!`) + .setThumbnail(member.user.displayAvatarURL()) + .setDescription( + getGuild.welcomeLeaveChannelMessage || + "Configure a leave message in the `/settings guild welcome`." + ) + .setTimestamp() + .setFooter({ + text: footerText, + iconURL: footerIcon, + }), + ], + }); + }, +}; diff --git a/src/plugins/events/interactionCreate/audits.ts b/src/events/interactionCreate/audits.ts similarity index 54% rename from src/plugins/events/interactionCreate/audits.ts rename to src/events/interactionCreate/audits.ts index 183ff5a..90a55e4 100644 --- a/src/plugins/events/interactionCreate/audits.ts +++ b/src/events/interactionCreate/audits.ts @@ -1,39 +1,37 @@ -import logger from "../../../logger"; -import { Interaction, MessageEmbed, TextChannel } from "discord.js"; - -import guildSchema from "../../../models/guild"; - -import getEmbedConfig from "../../../helpers/getEmbedConfig"; +import { BaseInteraction, ChannelType, EmbedBuilder } from "discord.js"; +import prisma from "../../handlers/database"; +import getEmbedConfig from "../../helpers/getEmbedData"; +import logger from "../../middlewares/logger"; export default { - execute: async (interaction: Interaction) => { + execute: async (interaction: BaseInteraction) => { if (interaction === null) return; if (interaction.guild === null) return; + const getGuild = await prisma.guild.findUnique({ + where: { id: interaction.guild.id }, + }); + if (!getGuild) throw new Error("Guild not found"); + const { footerText, footerIcon, successColor } = await getEmbedConfig( interaction.guild ); - const guildData = await guildSchema.findOne({ - guildId: interaction.guild.id, - }); - const { client } = interaction; - if (guildData === null) return; + if (getGuild.auditsEnabled !== true) return; + if (!getGuild.auditsChannelId) return; - if (guildData.audits.status !== true) return; - if (!guildData.audits.channelId) return; + const channel = client.channels.cache.get(`${getGuild.auditsChannelId}`); - const channel = client.channels.cache.get(`${guildData.audits.channelId}`); + if (!channel) return; + if (channel.type !== ChannelType.GuildText) return; - if (channel === null) return; - - (channel as TextChannel) + channel .send({ embeds: [ - new MessageEmbed() + new EmbedBuilder() .setColor(successColor) .setDescription( ` @@ -51,15 +49,13 @@ export default { }), ], }) - .then(async () => { + .then(() => { logger.debug( `Audit log sent for event interactionCreate in guild ${interaction?.guild?.name} (${interaction?.guild?.id})` ); }) - .catch(async () => { - logger.error( - `Audit log failed to send for event interactionCreate in guild ${interaction?.guild?.name} (${interaction?.guild?.id})` - ); + .catch(() => { + logger.silly("Failed to send audit log for event interactionCreate"); }); }, }; diff --git a/src/plugins/events/interactionCreate/handlers/button/index.ts b/src/events/interactionCreate/handlers/button/index.ts similarity index 71% rename from src/plugins/events/interactionCreate/handlers/button/index.ts rename to src/events/interactionCreate/handlers/button/index.ts index 3373f09..e00365e 100644 --- a/src/plugins/events/interactionCreate/handlers/button/index.ts +++ b/src/events/interactionCreate/handlers/button/index.ts @@ -1,10 +1,9 @@ // Dependencies -import { Interaction } from "discord.js"; +import { BaseInteraction } from "discord.js"; +import { button as CooldownButton } from "../../../../handlers/cooldown"; +import deferReply from "../../../../handlers/deferReply"; -import deferReply from "../../../../../helpers/deferReply"; -import * as cooldown from "../../../../../helpers/cooldown"; - -export default async (interaction: Interaction) => { +export default async (interaction: BaseInteraction) => { if (!interaction.isButton()) return; const { guild, customId, memberPermissions } = interaction; @@ -30,7 +29,7 @@ export default async (interaction: Interaction) => { if (metadata.dmOnly && guild) throw new Error("This command is only available in DM"); - if (metadata.cooldown) await cooldown.button(interaction, metadata.cooldown); + if (metadata.cooldown) await CooldownButton(interaction, metadata.cooldown); await currentButton.execute(interaction); }; diff --git a/src/events/interactionCreate/handlers/command/index.ts b/src/events/interactionCreate/handlers/command/index.ts new file mode 100644 index 0000000..0e79da8 --- /dev/null +++ b/src/events/interactionCreate/handlers/command/index.ts @@ -0,0 +1,31 @@ +// Dependencies +import { ChatInputCommandInteraction } from "discord.js"; + +export default async (interaction: ChatInputCommandInteraction) => { + if (!interaction.isCommand()) return; + const { client, commandName } = interaction; + + const currentCommand = client.commands.get(commandName); + if (!currentCommand) throw new Error(`Unknown command ${commandName}`); + + // const metadata = await getCommandMetadata(interaction, currentCommand); + // await deferReply(interaction, metadata.ephemeral || false); + + // if (metadata.guildOnly && !interaction.guild) + // throw new Error("This command is guild only."); + + // if (metadata.dmOnly && interaction.guild) + // throw new Error("This command is only available in DM"); + + // if ( + // metadata.permissions && + // metadata.guildOnly && + // !interaction.memberPermissions?.has(metadata.permissions) + // ) + // throw new Error("You don't have the required permissions"); + + // if (metadata.cooldown) { + // await CooldownCommand(interaction, metadata.cooldown); + // } + await currentCommand.execute(interaction); +}; diff --git a/src/events/interactionCreate/handlers/index.ts b/src/events/interactionCreate/handlers/index.ts new file mode 100644 index 0000000..3adb884 --- /dev/null +++ b/src/events/interactionCreate/handlers/index.ts @@ -0,0 +1,39 @@ +import { + BaseInteraction, + ButtonInteraction, + ChatInputCommandInteraction, + CommandInteraction, + EmbedBuilder, +} from "discord.js"; +import capitalizeFirstLetter from "../../../helpers/capitalizeFirstLetter"; +import getEmbedConfig from "../../../helpers/getEmbedData"; +import button from "./button"; +import command from "./command"; + +// Send interactions to all available handlers +export const execute = async (interaction: BaseInteraction) => { + await button(interaction); + await command(interaction); +}; + +// Handle interactions from commands +export const handleCommandInteraction = async ( + interaction: CommandInteraction +) => { + const { errorColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + + await command(interaction).catch((err) => { + return interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setTitle(`[:x:] ${capitalizeFirstLetter(interaction.commandName)}`) + .setDescription(`${err}`) + .setColor(errorColor) + .setTimestamp(new Date()) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + }); +}; diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts new file mode 100644 index 0000000..dde7ab2 --- /dev/null +++ b/src/events/interactionCreate/index.ts @@ -0,0 +1,35 @@ +// 3rd party dependencies +import { + BaseInteraction, + CommandInteraction, + InteractionType, +} from "discord.js"; +import { IEventOptions } from "../../interfaces/EventOptions"; +import logger from "../../middlewares/logger"; +// Dependencies +import audits from "./audits"; +import { handleCommandInteraction as HandlersHandleCommandInteraction } from "./handlers"; + +export const options: IEventOptions = { + type: "on", +}; + +// Execute the event +export const execute = async (interaction: BaseInteraction) => { + const { guild, id } = interaction; + + logger?.silly( + `New interaction: ${id} in guild: ${guild?.name} (${guild?.id})` + ); + + await audits.execute(interaction); + + switch (interaction.type) { + case InteractionType.ApplicationCommand: + await HandlersHandleCommandInteraction(interaction); + break; + + default: + logger?.error(`Unknown interaction type: ${interaction.type}`); + } +}; diff --git a/src/plugins/events/messageCreate/index.ts b/src/events/messageCreate/index.ts similarity index 69% rename from src/plugins/events/messageCreate/index.ts rename to src/events/messageCreate/index.ts index 06619f0..6a745cc 100644 --- a/src/plugins/events/messageCreate/index.ts +++ b/src/events/messageCreate/index.ts @@ -1,12 +1,12 @@ import { Message } from "discord.js"; -import modules from "../../events/messageCreate/modules"; - -import { IEventOptions } from "../../../interfaces/EventOptions"; +import { IEventOptions } from "../../interfaces/EventOptions"; +import modules from "./modules"; export const options: IEventOptions = { type: "on", }; +// Execute the function export const execute = async (message: Message) => { await modules.credits.execute(message); await modules.points.execute(message); diff --git a/src/events/messageCreate/modules/counters/index.ts b/src/events/messageCreate/modules/counters/index.ts new file mode 100644 index 0000000..2fceb28 --- /dev/null +++ b/src/events/messageCreate/modules/counters/index.ts @@ -0,0 +1,66 @@ +import { ChannelType, Message } from "discord.js"; +import prisma from "../../../../handlers/database"; +import logger from "../../../../middlewares/logger"; + +export default { + execute: async (message: Message) => { + const { guild, author, content, channel } = message; + + if (!guild) return; + if (author.bot) return; + if (channel?.type !== ChannelType.GuildText) return; + + const messages = await message.channel.messages.fetch({ limit: 2 }); + const lastMessage = messages.last(); + + const channelCounter = await prisma.guildCounter.findUnique({ + where: { + guildId_channelId: { + guildId: guild.id, + channelId: channel.id, + }, + }, + }); + + if (!channelCounter) throw new Error("No counters found in channel."); + + if ( + lastMessage?.author.id === author.id && + channel.id === channelCounter.channelId + ) { + logger.silly( + `${author.username} sent the last message therefor not allowing again.` + ); + await message.delete(); + return; + } + + if (content !== channelCounter.triggerWord) { + logger.silly( + `Counter word ${channelCounter.triggerWord} does not match message ${content}` + ); + + await message.delete(); + return; + } + + const updateGuildCounter = await prisma.guildCounter.update({ + where: { + guildId_channelId: { + guildId: guild.id, + channelId: channel.id, + }, + }, + data: { + count: { + increment: 1, + }, + }, + }); + + logger.silly(updateGuildCounter); + + if (!updateGuildCounter) + logger.error(`Failed to update counter - ${updateGuildCounter}`); + }, +}; diff --git a/src/events/messageCreate/modules/credits/index.ts b/src/events/messageCreate/modules/credits/index.ts new file mode 100644 index 0000000..2ae1ac8 --- /dev/null +++ b/src/events/messageCreate/modules/credits/index.ts @@ -0,0 +1,80 @@ +import { ChannelType, Message } from "discord.js"; +import { message as CooldownMessage } from "../../../../handlers/cooldown"; +import prisma from "../../../../handlers/database"; +import logger from "../../../../middlewares/logger"; + +export default { + execute: async (message: Message) => { + const { guild, author, content, channel } = message; + + if (!guild) return; + if (author.bot) return; + if (channel.type !== ChannelType.GuildText) return; + + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: author.id, + guildId: guild.id, + }, + }, + update: {}, + create: { + user: { + connectOrCreate: { + create: { + id: author.id, + }, + where: { + id: author.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, + include: { + user: true, + guild: true, + }, + }); + + logger.silly(createGuildMember); + + if (content.length < createGuildMember.guild.creditsMinimumLength) return; + + const isOnCooldown = await CooldownMessage( + message, + createGuildMember.guild.creditsTimeout, + "messageCreate-credits" + ); + if (isOnCooldown) return; + + const updateGuildMember = await prisma.guildMember.update({ + where: { + userId_guildId: { + userId: author.id, + guildId: guild.id, + }, + }, + data: { + creditsEarned: { + increment: createGuildMember.guild.creditsRate, + }, + }, + }); + + logger.silly(updateGuildMember); + + if (!updateGuildMember) + throw new Error("Failed to update guildMember object"); + }, +}; diff --git a/src/plugins/events/messageCreate/modules/index.ts b/src/events/messageCreate/modules/index.ts similarity index 100% rename from src/plugins/events/messageCreate/modules/index.ts rename to src/events/messageCreate/modules/index.ts diff --git a/src/events/messageCreate/modules/points/index.ts b/src/events/messageCreate/modules/points/index.ts new file mode 100644 index 0000000..583b6fa --- /dev/null +++ b/src/events/messageCreate/modules/points/index.ts @@ -0,0 +1,80 @@ +import { ChannelType, Message } from "discord.js"; +import { message as CooldownMessage } from "../../../../handlers/cooldown"; +import prisma from "../../../../handlers/database"; +import logger from "../../../../middlewares/logger"; + +export default { + execute: async (message: Message) => { + const { guild, author, content, channel } = message; + + if (!guild) return; + if (author.bot) return; + if (channel.type !== ChannelType.GuildText) return; + + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: author.id, + guildId: guild.id, + }, + }, + update: {}, + create: { + user: { + connectOrCreate: { + create: { + id: author.id, + }, + where: { + id: author.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, + include: { + user: true, + guild: true, + }, + }); + + logger.silly(createGuildMember); + + if (content.length < createGuildMember.guild.pointsMinimumLength) return; + + const isOnCooldown = await CooldownMessage( + message, + createGuildMember.guild.pointsTimeout, + "messageCreate-points" + ); + if (isOnCooldown) return; + + const updateGuildMember = await prisma.guildMember.update({ + where: { + userId_guildId: { + userId: author.id, + guildId: guild.id, + }, + }, + data: { + pointsEarned: { + increment: createGuildMember.guild.pointsRate, + }, + }, + }); + + logger.silly(updateGuildMember); + + if (!updateGuildMember) + throw new Error("Failed to update guildMember object"); + }, +}; diff --git a/src/plugins/events/messageDelete/audits.ts b/src/events/messageDelete/audits.ts similarity index 59% rename from src/plugins/events/messageDelete/audits.ts rename to src/events/messageDelete/audits.ts index 4b5d5a2..56a8ac1 100644 --- a/src/plugins/events/messageDelete/audits.ts +++ b/src/events/messageDelete/audits.ts @@ -1,9 +1,7 @@ -import logger from "../../../logger"; -import { Message, MessageEmbed, TextChannel } from "discord.js"; - -import guildSchema from "../../../models/guild"; - -import getEmbedConfig from "../../../helpers/getEmbedConfig"; +import { ChannelType, EmbedBuilder, Message } from "discord.js"; +import prisma from "../../handlers/database"; +import getEmbedConfig from "../../helpers/getEmbedData"; +import logger from "../../middlewares/logger"; export default { execute: async (message: Message) => { @@ -15,25 +13,27 @@ export default { message.guild ); - const guildData = await guildSchema.findOne({ - guildId: message.guild.id, + const getGuild = await prisma.guild.findUnique({ + where: { id: message.guild.id }, }); + if (!getGuild) throw new Error("Guild not found"); const { client } = message; - if (guildData === null) return; + if (!getGuild) throw new Error("Guild not found"); - if (guildData.audits.status !== true) return; - if (!guildData.audits.channelId) return; + if (getGuild.auditsEnabled !== true) return; + if (!getGuild.auditsChannelId) return; - const channel = client.channels.cache.get(`${guildData.audits.channelId}`); + const channel = client.channels.cache.get(`${getGuild.auditsChannelId}`); - if (channel === null) return; + if (!channel) return; + if (channel.type !== ChannelType.GuildText) return; - (channel as TextChannel) + channel .send({ embeds: [ - new MessageEmbed() + new EmbedBuilder() .setColor(successColor) .setAuthor({ name: message.author.username, @@ -52,13 +52,13 @@ export default { }), ], }) - .then(async () => { + .then(() => { logger.info( `Audit log sent for event messageDelete in guild ${message?.guild?.name} (${message?.guild?.id})` ); }) - .catch(async () => { - logger.error( + .catch(() => { + throw new Error( `Audit log failed to send for event messageDelete in guild ${message?.guild?.name} (${message?.guild?.id})` ); }); diff --git a/src/plugins/events/messageDelete/index.ts b/src/events/messageDelete/index.ts similarity index 67% rename from src/plugins/events/messageDelete/index.ts rename to src/events/messageDelete/index.ts index 3cb7f6e..53ccb2d 100644 --- a/src/plugins/events/messageDelete/index.ts +++ b/src/events/messageDelete/index.ts @@ -1,12 +1,13 @@ import { Message } from "discord.js"; -import audits from "../../events/messageDelete/audits"; +import { IEventOptions } from "../../interfaces/EventOptions"; +import audits from "./audits"; import counter from "./modules/counter"; -import { IEventOptions } from "../../../interfaces/EventOptions"; export const options: IEventOptions = { type: "on", }; +// Execute the function export const execute = async (message: Message) => { await audits.execute(message); await counter(message); diff --git a/src/events/messageDelete/modules/counter.ts b/src/events/messageDelete/modules/counter.ts new file mode 100644 index 0000000..bbb058c --- /dev/null +++ b/src/events/messageDelete/modules/counter.ts @@ -0,0 +1,38 @@ +// Dependencies +import { Message } from "discord.js"; +// Models +import prisma from "../../../handlers/database"; +import logger from "../../../middlewares/logger"; + +export default async (message: Message) => { + const { guild, channel, author, content } = message; + + if (!guild) throw new Error("Guild not found"); + if (!channel) throw new Error("Channel not found"); + + const channelCounter = await prisma.guildCounter.findUnique({ + where: { + guildId_channelId: { + guildId: guild.id, + channelId: channel.id, + }, + }, + }); + + if (!channelCounter) throw new Error("No counter found in channel."); + + const messages = await message.channel.messages.fetch({ limit: 1 }); + const lastMessage = messages.last(); + + if (!lastMessage) return; + + if (content !== channelCounter.triggerWord) return; + + if (lastMessage.author.id === message.author.id) return; + + channel?.send(`${author} said **${channelCounter.triggerWord}**.`); + logger?.silly(`${author} said ${channelCounter.triggerWord} in ${channel}`); + return logger?.silly( + `User: ${author?.tag} (${author?.id}) in guild: ${guild?.name} (${guild?.id}) said the counter word: ${channelCounter.triggerWord}` + ); +}; diff --git a/src/plugins/events/messageUpdate/audits.ts b/src/events/messageUpdate/audits.ts similarity index 65% rename from src/plugins/events/messageUpdate/audits.ts rename to src/events/messageUpdate/audits.ts index 515a090..a498385 100644 --- a/src/plugins/events/messageUpdate/audits.ts +++ b/src/events/messageUpdate/audits.ts @@ -1,10 +1,8 @@ /* eslint-disable no-loops/no-loops */ -import logger from "../../../logger"; -import { Message, MessageEmbed, TextChannel } from "discord.js"; - -import guildSchema from "../../../models/guild"; - -import getEmbedConfig from "../../../helpers/getEmbedConfig"; +import { ChannelType, EmbedBuilder, Message } from "discord.js"; +import prisma from "../../handlers/database"; +import getEmbedConfig from "../../helpers/getEmbedData"; +import logger from "../../middlewares/logger"; export default { execute: async (oldMessage: Message, newMessage: Message) => { @@ -18,25 +16,25 @@ export default { newMessage.guild ); - const guildData = await guildSchema.findOne({ - guildId: oldMessage.guild.id, + const getGuild = await prisma.guild.findUnique({ + where: { id: oldMessage.guild.id }, }); + if (!getGuild) throw new Error("Guild not found"); const { client } = oldMessage; - if (guildData === null) return; + if (getGuild.auditsEnabled !== true) return; + if (!getGuild.auditsChannelId) return; - if (guildData.audits.status !== true) return; - if (!guildData.audits.channelId) return; + const channel = client.channels.cache.get(`${getGuild.auditsChannelId}`); - const channel = client.channels.cache.get(`${guildData.audits.channelId}`); + if (!channel) return; + if (channel.type !== ChannelType.GuildText) return; - if (channel === null) return; - - (channel as TextChannel) + channel .send({ embeds: [ - new MessageEmbed() + new EmbedBuilder() .setColor(successColor) .setAuthor({ name: newMessage.author.username, @@ -54,13 +52,13 @@ export default { }), ], }) - .then(async () => { + .then(() => { logger.info( `Audit log sent for event messageUpdate in guild ${newMessage?.guild?.name} (${newMessage?.guild?.id})` ); }) - .catch(async () => { - logger.error( + .catch(() => { + throw new Error( `Audit log failed to send for event messageUpdate in guild ${newMessage?.guild?.name} (${newMessage?.guild?.id})` ); }); diff --git a/src/plugins/events/messageUpdate/index.ts b/src/events/messageUpdate/index.ts similarity index 81% rename from src/plugins/events/messageUpdate/index.ts rename to src/events/messageUpdate/index.ts index 9c9bf47..80d3c06 100644 --- a/src/plugins/events/messageUpdate/index.ts +++ b/src/events/messageUpdate/index.ts @@ -1,17 +1,18 @@ // Dependencies import { Message } from "discord.js"; -import logger from "../../../logger"; +import logger from "../../middlewares/logger"; // Modules import counter from "./modules/counter"; +import { IEventOptions } from "../../interfaces/EventOptions"; import audits from "./audits"; -import { IEventOptions } from "../../../interfaces/EventOptions"; export const options: IEventOptions = { type: "on", }; +// Execute the function export const execute = async (oldMessage: Message, newMessage: Message) => { const { author, guild } = newMessage; diff --git a/src/events/messageUpdate/modules/counter.ts b/src/events/messageUpdate/modules/counter.ts new file mode 100644 index 0000000..d1586be --- /dev/null +++ b/src/events/messageUpdate/modules/counter.ts @@ -0,0 +1,41 @@ +// Dependencies +import { Message } from "discord.js"; +// Models +import prisma from "../../../handlers/database"; +import logger from "../../../middlewares/logger"; + +export default async (message: Message) => { + const { guild, channel, author, content } = message; + + if (!guild) throw new Error("Guild not found"); + + if (!channel) throw new Error("Channel not found"); + + const channelCounter = await prisma.guildCounter.findUnique({ + where: { + guildId_channelId: { + guildId: guild.id, + channelId: channel.id, + }, + }, + }); + + if (!channelCounter) throw new Error("No counters found in channel."); + + if (content === channelCounter.triggerWord) + return logger?.silly( + `User: ${author?.tag} (${author?.id}) in guild: ${guild?.name} (${guild?.id}) said the counter word: ${channelCounter.triggerWord}` + ); + + await message + .delete() + .then(async () => { + await channel?.send(`${author} said **${channelCounter.triggerWord}**.`); + logger?.silly( + `${author} said ${channelCounter.triggerWord} in ${channel}` + ); + }) + .catch((error) => { + logger.error(error); + }); +}; diff --git a/src/plugins/events/rateLimit/index.ts b/src/events/rateLimit/index.ts similarity index 52% rename from src/plugins/events/rateLimit/index.ts rename to src/events/rateLimit/index.ts index ca9aab5..059b855 100644 --- a/src/plugins/events/rateLimit/index.ts +++ b/src/events/rateLimit/index.ts @@ -1,14 +1,14 @@ // Dependencies import { Client } from "discord.js"; -import logger from "../../../logger"; - // Helpers -import { IEventOptions } from "../../../interfaces/EventOptions"; +import { IEventOptions } from "../../interfaces/EventOptions"; +import logger from "../../middlewares/logger"; export const options: IEventOptions = { type: "on", }; -export const execute = async (client: Client) => { +// Function to execute the event +export const execute = (client: Client) => { logger.warn(`Discord's API client (${client?.user?.tag}) is rate-limited!`); }; diff --git a/src/events/ready/index.ts b/src/events/ready/index.ts new file mode 100644 index 0000000..b91a36a --- /dev/null +++ b/src/events/ready/index.ts @@ -0,0 +1,21 @@ +// Dependencies +import { Client } from "discord.js"; +// Helpers +import deployCommands from "../../handlers/deployCommands"; +import devMode from "../../handlers/devMode"; +import updatePresence from "../../handlers/updatePresence"; +import { IEventOptions } from "../../interfaces/EventOptions"; +import logger from "../../middlewares/logger"; + +export const options: IEventOptions = { + type: "once", +}; + +// Execute the event +export const execute = async (client: Client) => { + logger.info("Discord's API client is ready!"); + + updatePresence(client); + await devMode(client); + await deployCommands(client); +}; diff --git a/src/handlers/command/index.ts b/src/handlers/command/index.ts new file mode 100644 index 0000000..09d05e2 --- /dev/null +++ b/src/handlers/command/index.ts @@ -0,0 +1,54 @@ +import { Client } from "discord.js"; +import listDir from "../../helpers/checkDirectory"; +import { ICommand } from "../../interfaces/Command"; +import logger from "../../middlewares/logger"; + +export const register = async (client: Client) => { + // Get name of directories containing commands + const commandNames = await listDir("commands"); + if (!commandNames) throw new Error("📦 No commands available"); + + const amountOfCommands = commandNames.length; + let importedCommandAmount = 0; + logger.info(`📦 Trying to load ${amountOfCommands} commands`); + + const importCommand = async (commandName: string) => { + // Import command from commands + const command: ICommand = await import(`../../commands/${commandName}`); + if (!command) + throw new Error(`📦 No command found while importing "${commandName}"`); + if (!command.builder) + throw new Error( + `📦 No command builder found while importing "${commandName}"` + ); + + // Add command to collection + client.commands.set(command.builder.name, command); + importedCommandAmount += 1; + }; + + // Send log message when it's done loading commands + const doneImporting = () => { + if (importedCommandAmount !== amountOfCommands) { + return logger.warn( + `📦 Failed importing ${ + amountOfCommands - importedCommandAmount + } of ${amountOfCommands} commands` + ); + } + + return logger.info(`📦 Managed to load all commands`); + }; + + // Start importing commands + commandNames.forEach(async (commandName: string, index: number) => { + await importCommand(commandName).then(() => { + logger.debug(`📦 Imported the "${commandName}" command`); + }); + + // If done importing + if (index + 1 === amountOfCommands) { + await doneImporting(); + } + }); +}; diff --git a/src/handlers/cooldown/index.ts b/src/handlers/cooldown/index.ts new file mode 100644 index 0000000..6721ccd --- /dev/null +++ b/src/handlers/cooldown/index.ts @@ -0,0 +1,277 @@ +// Dependencies +import { ButtonInteraction, CommandInteraction, Message } from "discord.js"; +import addSeconds from "../../helpers/addSeconds"; +import logger from "../../middlewares/logger"; +import prisma from "../database"; + +// Command cooldown +export const command = async (i: CommandInteraction, cooldown: number) => { + const { guild, user, commandId } = i; + + if (!guild) throw new Error("Guild not found"); + + // Check if user has a timeout + const hasTimeout = await prisma.cooldown.findUnique({ + where: { + guildId_userId_timeoutId: { + guildId: guild.id, + userId: user.id, + timeoutId: commandId, + }, + }, + }); + + logger.silly(hasTimeout); + + // If user is not on timeout + if (hasTimeout) { + const { userId, timeoutId, createdAt } = hasTimeout; + const overDue = addSeconds(cooldown, createdAt) < new Date(); + + if (!overDue) { + const diff = Math.round( + (new Date(hasTimeout.createdAt).getTime() - new Date().getTime()) / 1000 + ); + + throw new Error( + `You must wait ${diff} seconds before using this command.` + ); + } + + // Delete timeout + const deleteCooldown = await prisma.cooldown.delete({ + where: { + guildId_userId_timeoutId: { + guildId: guild.id, + userId: user.id, + timeoutId: commandId, + }, + }, + }); + + logger.silly(deleteCooldown); + + logger.debug( + `Timeout document ${timeoutId} has been deleted from user ${userId}.` + ); + } + + // Create timeout + const createCooldown = await prisma.cooldown.upsert({ + where: { + guildId_userId_timeoutId: { + userId: user.id, + guildId: guild.id, + timeoutId: commandId, + }, + }, + update: {}, + create: { + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + user: { + connectOrCreate: { + create: { + id: user.id, + }, + where: { + id: user.id, + }, + }, + }, + timeoutId: commandId, + cooldown, + }, + }); + + logger.silly(createCooldown); +}; + +// Button cooldown +export const button = async (i: ButtonInteraction, cooldown: number) => { + const { guild, user, customId } = i; + + if (!guild) throw new Error("Guild not found"); + + // Check if user has a timeout + const hasTimeout = await prisma.cooldown.findUnique({ + where: { + guildId_userId_timeoutId: { + guildId: guild.id, + userId: user.id, + timeoutId: customId, + }, + }, + }); + + logger.silly(hasTimeout); + + // If user is not on timeout + if (hasTimeout) { + const { userId, timeoutId, createdAt } = hasTimeout; + const overDue = addSeconds(cooldown, createdAt) < new Date(); + + if (!overDue) { + const diff = Math.round( + (new Date(hasTimeout.createdAt).getTime() - new Date().getTime()) / 1000 + ); + + throw new Error( + `You must wait ${diff} seconds before using this command.` + ); + } + + // Delete timeout + const deleteCooldown = await prisma.cooldown.delete({ + where: { + guildId_userId_timeoutId: { + guildId: guild.id, + userId: user.id, + timeoutId: customId, + }, + }, + }); + + logger.silly(deleteCooldown); + + logger.debug( + `Timeout document ${timeoutId} has been deleted from user ${userId}.` + ); + } + + // Create timeout + const createCooldown = await prisma.cooldown.upsert({ + where: { + guildId_userId_timeoutId: { + userId: user.id, + guildId: guild.id, + timeoutId: customId, + }, + }, + update: {}, + create: { + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + user: { + connectOrCreate: { + create: { + id: user.id, + }, + where: { + id: user.id, + }, + }, + }, + timeoutId: customId, + cooldown, + }, + }); + + logger.silly(createCooldown); +}; + +// Message cooldown +export const message = async (msg: Message, cooldown: number, id: string) => { + const { guild, member } = msg; + + if (!guild) throw new Error("Guild not found"); + if (!member) throw new Error("Member is undefined"); + + // Check if user has a timeout + const hasTimeout = await prisma.cooldown.findUnique({ + where: { + guildId_userId_timeoutId: { + guildId: guild.id, + userId: member.id, + timeoutId: id, + }, + }, + }); + + logger.silly(hasTimeout); + + // If user is not on timeout + if (hasTimeout) { + const { userId, timeoutId, createdAt } = hasTimeout; + const overDue = addSeconds(cooldown, createdAt) < new Date(); + + if (!overDue) { + const diff = Math.round( + (new Date(hasTimeout.createdAt).getTime() - new Date().getTime()) / 1000 + ); + + return `User: ${userId} on timeout-id: ${id} with cooldown: ${cooldown} secs with remaining: ${diff} secs.`; + } + + // Delete timeout + const deleteCooldown = await prisma.cooldown.delete({ + where: { + guildId_userId_timeoutId: { + guildId: guild.id, + userId: member.id, + timeoutId: id, + }, + }, + }); + + logger.silly(deleteCooldown); + + logger.debug( + `Timeout document ${timeoutId} has been deleted from user ${userId}.` + ); + } + + // Create timeout + const createCooldown = await prisma.cooldown.upsert({ + where: { + guildId_userId_timeoutId: { + userId: member.id, + guildId: guild.id, + timeoutId: id, + }, + }, + update: {}, + create: { + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + user: { + connectOrCreate: { + create: { + id: member.id, + }, + where: { + id: member.id, + }, + }, + }, + timeoutId: id, + cooldown, + }, + }); + + logger.silly(createCooldown); +}; diff --git a/src/handlers/database/index.ts b/src/handlers/database/index.ts new file mode 100644 index 0000000..c4e22e4 --- /dev/null +++ b/src/handlers/database/index.ts @@ -0,0 +1,3 @@ +import { PrismaClient } from '@prisma/client' + +export default new PrismaClient() diff --git a/src/helpers/deferReply/index.ts b/src/handlers/deferReply/index.ts similarity index 72% rename from src/helpers/deferReply/index.ts rename to src/handlers/deferReply/index.ts index 02cc413..e795799 100644 --- a/src/helpers/deferReply/index.ts +++ b/src/handlers/deferReply/index.ts @@ -1,7 +1,7 @@ -import { Interaction, MessageEmbed } from "discord.js"; -import getEmbedConfig from "../../helpers/getEmbedConfig"; +import { BaseInteraction, EmbedBuilder } from "discord.js"; +import getEmbedConfig from "../../helpers/getEmbedData"; -export default async (interaction: Interaction, ephemeral: boolean) => { +export default async (interaction: BaseInteraction, ephemeral: boolean) => { if (!interaction.isRepliable()) throw new Error(`Cannot reply to an interaction that is not repliable`); @@ -13,7 +13,7 @@ export default async (interaction: Interaction, ephemeral: boolean) => { await interaction.editReply({ embeds: [ - new MessageEmbed() + new EmbedBuilder() .setFooter({ text: embedConfig.footerText, iconURL: embedConfig.footerIcon, diff --git a/src/handlers/deployCommands/index.ts b/src/handlers/deployCommands/index.ts index 492e1ab..4b30c96 100644 --- a/src/handlers/deployCommands/index.ts +++ b/src/handlers/deployCommands/index.ts @@ -1,13 +1,7 @@ -import { token, clientId } from "../../config/discord"; -import { devMode, guildId } from "../../config/other"; - -import logger from "../../logger"; -import { Client } from "discord.js"; -import { REST } from "@discordjs/rest"; -import { Routes } from "discord-api-types/v9"; import { RESTPostAPIApplicationCommandsJSONBody } from "discord-api-types/v10"; - +import { Client } from "discord.js"; import { ICommand } from "../../interfaces/Command"; +import logger from "../../middlewares/logger"; export default async (client: Client) => { const commandList: Array = []; @@ -19,40 +13,28 @@ export default async (client: Client) => { logger.info("Gathering command list"); await Promise.all( - client.commands.map(async (commandData: ICommand) => { + client.commands.map((commandData: ICommand) => { commandList.push(commandData.builder.toJSON()); logger.verbose(`${commandData.builder.name} pushed to list`); }) ) - .then(async () => { + .then(() => { logger.info(`Finished gathering command list.`); }) - .catch(async (error) => { + .catch((error) => { throw new Error(`Could not gather command list: ${error}`); }); - const rest = new REST({ version: "9" }).setToken(token); - - await rest - .put(Routes.applicationCommands(clientId), { - body: commandList, - }) - .then(async () => { + await client.application?.commands + .set(commandList, process.env.DISCORD_GUILD_ID) + .then(() => { logger.info(`Finished updating command list.`); - }) - .catch(async (error) => { - logger.error(`${error}`); }); - if (devMode) { - await rest - .put(Routes.applicationGuildCommands(clientId, guildId), { - body: commandList, - }) - .then(async () => logger.info(`Finished updating guild command list.`)) - .catch(async (error) => { - logger.error(`${error}`); - }); + if (process.env.NODE_ENV !== "production") { + await client.application?.commands + .set(commandList) + .then(() => logger.info(`Finished updating guild command list.`)); } }; diff --git a/src/handlers/devMode/index.ts b/src/handlers/devMode/index.ts index c7900a0..1c69b66 100644 --- a/src/handlers/devMode/index.ts +++ b/src/handlers/devMode/index.ts @@ -1,16 +1,13 @@ -// Dependencies import { Client } from "discord.js"; - -import logger from "../../logger"; - -// Configuration -import { devMode, guildId } from "../../config/other"; +import logger from "../../middlewares/logger"; export default async (client: Client) => { - if (!devMode) { - return client?.application?.commands?.set([], guildId).then(async () => { - return logger.verbose(`Development mode is disabled.`); - }); + if (process.env.NODE_ENV !== "production") { + await client?.application?.commands + ?.set([], process.env.DISCORD_GUILD_ID) + .then(() => { + return logger.verbose(`Development mode is disabled.`); + }); } return logger.info(`Development mode is enabled.`); diff --git a/src/handlers/event/index.ts b/src/handlers/event/index.ts new file mode 100644 index 0000000..4d0fabc --- /dev/null +++ b/src/handlers/event/index.ts @@ -0,0 +1,96 @@ +/* eslint-disable no-loops/no-loops */ +import { Client } from "discord.js"; +import listDir from "../../helpers/checkDirectory"; +import { IEvent } from "../../interfaces/Event"; +import logger from "../../middlewares/logger"; + +// Registers all available events +export const register = async (client: Client) => { + const eventNames = await listDir("events"); + if (!eventNames) throw new Error("📦 No events available"); + + const amountOfEvents = eventNames.length; + let importedEventAmount = 0; + logger.info(`📦 Trying to load ${amountOfEvents} events`); + + const importEvent = async (eventName: string) => { + // Import event from events + const event: IEvent = await import(`../../events/${eventName}`); + if (!event) + throw new Error(`📦 No event found while importing "${eventName}"`); + if (!event.options) + throw new Error( + `📦 No event options found while importing "${eventName}"` + ); + if (!event.execute) + throw new Error( + `📦 No event execute found while importing "${eventName}"` + ); + + // Register event + const eventExecutor = async (...args: Promise[]) => { + await event.execute(...args).catch((err) => { + logger.error(`${err}`); + }); + }; + + if (!event.options?.type) + throw new Error(`📦 No event type found while importing "${eventName}"`); + + switch (event.options.type) { + case "once": + client.once(eventName, eventExecutor); + break; + + case "on": + client.on(eventName, eventExecutor); + break; + default: + logger.error(`${eventName} does not have a valid type`); + } + importedEventAmount += 1; + }; + + // Send log message when it's done loading events + const doneImporting = () => { + if (importedEventAmount !== amountOfEvents) { + return logger.warn( + `📦 Failed importing ${ + amountOfEvents - importedEventAmount + } of ${amountOfEvents} events` + ); + } + + return logger.info(`📦 Managed to load all events`); + }; + + eventNames.forEach(async (eventName: string, index: number) => { + await importEvent(eventName).then(() => { + logger.debug(`📦 Imported the "${eventName}" event`); + }); + + // If done importing + if (index + 1 === amountOfEvents) { + await doneImporting(); + } + }); + + // for await (const eventName of eventNames) { + // const event: IEvent = await import(`../../events/${eventName}`); + // const eventExecutor = async (...args: Promise[]) => + // event.execute(...args).catch(async (err) => { + // logger.error(`${err}`); + // }); + // if (!event.options?.type) return; + + // switch (event.options.type) { + // case "once": + // client.once(eventName, eventExecutor); + // break; + + // case "on": + // client.on(eventName, eventExecutor); + // break; + // } + // } +}; diff --git a/src/handlers/schedule/index.ts b/src/handlers/schedule/index.ts new file mode 100644 index 0000000..da2c37d --- /dev/null +++ b/src/handlers/schedule/index.ts @@ -0,0 +1,24 @@ +import { Client } from "discord.js"; +import schedule from "node-schedule"; +import checkDirectory from "../../helpers/checkDirectory"; +import { IJob } from "../../interfaces/Job"; +import logger from "../../middlewares/logger"; + +// Start all jobs that are in the schedules directory +export const start = async (client: Client) => { + logger.info("⏰ Started job management"); + + const jobNames = await checkDirectory("schedules"); + if (!jobNames) return logger.warn("No available jobs found"); + + await Promise.all( + jobNames.map(async (jobName) => { + const job: IJob = await import(`../../schedules/${jobName}`); + + schedule.scheduleJob(job.options.schedule, async () => { + logger.verbose(`⏰ Performed the job "${jobName}"`); + await job.execute(client); + }); + }) + ); +}; diff --git a/src/helpers/updatePresence/index.ts b/src/handlers/updatePresence/index.ts similarity index 68% rename from src/helpers/updatePresence/index.ts rename to src/handlers/updatePresence/index.ts index 334a3e2..55a7d95 100644 --- a/src/helpers/updatePresence/index.ts +++ b/src/handlers/updatePresence/index.ts @@ -1,9 +1,9 @@ // Dependencies -import { Client } from "discord.js"; -import logger from "../../logger"; +import { ActivityType, Client } from "discord.js"; +import logger from "../../middlewares/logger"; // Function -export default async (client: Client) => { +export default (client: Client) => { if (!client?.user) throw new Error("Client's user is undefined."); const { guilds } = client; @@ -12,7 +12,7 @@ export default async (client: Client) => { const status = `${memberCount} users in ${guildCount} guilds.`; client.user.setPresence({ - activities: [{ type: "LISTENING", name: status }], + activities: [{ type: ActivityType.Watching, name: status }], status: "online", }); diff --git a/src/helpers/addSeconds/index.ts b/src/helpers/addSeconds/index.ts index 6c47b18..baa7d83 100644 --- a/src/helpers/addSeconds/index.ts +++ b/src/helpers/addSeconds/index.ts @@ -1,4 +1,4 @@ -export default async (seconds: number, date: Date) => { +export default (seconds: number, date: Date) => { date.setSeconds(date.getSeconds() + seconds); return date; }; diff --git a/src/helpers/checkDirectory/index.ts b/src/helpers/checkDirectory/index.ts new file mode 100644 index 0000000..de33ba1 --- /dev/null +++ b/src/helpers/checkDirectory/index.ts @@ -0,0 +1,7 @@ +import fs from "fs"; +const fsPromises = fs.promises; + +export default async (path: string) => { + const result = await fsPromises.readdir(path); + return result; +}; diff --git a/src/helpers/checkPermission/index.ts b/src/helpers/checkPermission/index.ts new file mode 100644 index 0000000..a14c82d --- /dev/null +++ b/src/helpers/checkPermission/index.ts @@ -0,0 +1,12 @@ +import { BaseInteraction, PermissionResolvable } from "discord.js"; + +export default ( + interaction: BaseInteraction, + permission: PermissionResolvable +) => { + if (!interaction.memberPermissions) + throw new Error("Could not check user for permissions"); + + if (!interaction.memberPermissions.has(permission)) + throw new Error("Permission denied"); +}; diff --git a/src/helpers/cooldown/index.ts b/src/helpers/cooldown/index.ts deleted file mode 100644 index f574f57..0000000 --- a/src/helpers/cooldown/index.ts +++ /dev/null @@ -1,156 +0,0 @@ -// Dependencies -import { CommandInteraction, ButtonInteraction, Message } from "discord.js"; - -import logger from "../../logger"; - -import timeoutSchema from "../../models/timeout"; -import addSeconds from "../../helpers/addSeconds"; - -export const command = async (i: CommandInteraction, cooldown: number) => { - const { guild, user, commandId } = i; - - // Check if user has a timeout - const hasTimeout = await timeoutSchema.findOne({ - guildId: guild?.id || "0", - userId: user.id, - cooldown: cooldown, - timeoutId: commandId, - }); - - // If user is not on timeout - if (hasTimeout) { - const { guildId, userId, timeoutId, createdAt } = hasTimeout; - const overDue = (await addSeconds(cooldown, createdAt)) < new Date(); - - if (!overDue) { - const diff = Math.round( - (new Date(hasTimeout.createdAt).getTime() - new Date().getTime()) / 1000 - ); - - throw new Error( - `You must wait ${diff} seconds before using this command.` - ); - } - - // Delete timeout - await timeoutSchema - .deleteOne({ - guildId, - userId, - timeoutId, - cooldown, - }) - .then(async () => { - logger.debug( - `Timeout document ${timeoutId} has been deleted from user ${userId}.` - ); - }); - } - // Create timeout - await timeoutSchema.create({ - guildId: guild?.id || "0", - userId: user.id, - cooldown: cooldown, - timeoutId: commandId, - }); -}; - -export const button = async (i: ButtonInteraction, cooldown: number) => { - const { guild, user, customId } = i; - - // Check if user has a timeout - const hasTimeout = await timeoutSchema.findOne({ - guildId: guild?.id || "0", - userId: user.id, - cooldown: cooldown, - timeoutId: customId, - }); - - // If user is not on timeout - if (hasTimeout) { - const { guildId, userId, timeoutId, createdAt } = hasTimeout; - const overDue = (await addSeconds(cooldown, createdAt)) < new Date(); - - if (!overDue) { - const diff = Math.round( - (new Date(hasTimeout.createdAt).getTime() - new Date().getTime()) / 1000 - ); - - throw new Error( - `You must wait ${diff} seconds before using this command.` - ); - } - - // Delete timeout - await timeoutSchema - .deleteOne({ - guildId, - userId, - timeoutId, - cooldown, - }) - .then(async () => { - logger.debug( - `Timeout document ${timeoutId} has been deleted from user ${userId}.` - ); - }); - } - // Create timeout - await timeoutSchema.create({ - guildId: guild?.id || "0", - userId: user.id, - cooldown: cooldown, - timeoutId: customId, - }); -}; - -export const message = async (msg: Message, cooldown: number, id: string) => { - const { guild, member } = msg; - if (!guild) throw new Error("Guild is undefined"); - if (!member) throw new Error("Member is undefined"); - - // Check if user has a timeout - const hasTimeout = await timeoutSchema.findOne({ - guildId: guild?.id || "0", - userId: member.id, - cooldown: cooldown, - timeoutId: id, - }); - - // If user is not on timeout - if (hasTimeout) { - const { guildId, userId, timeoutId, createdAt } = hasTimeout; - const overDue = (await addSeconds(cooldown, createdAt)) < new Date(); - - if (!overDue) { - const diff = Math.round( - (new Date(hasTimeout.createdAt).getTime() - new Date().getTime()) / 1000 - ); - - throw new Error( - `User: ${userId} on timeout-id: ${id} with cooldown: ${cooldown} secs with remaining: ${diff} secs.` - ); - } - - // Delete timeout - await timeoutSchema - .deleteOne({ - guildId, - userId: member.id, - timeoutId: id, - cooldown, - }) - .then(async () => { - logger.debug( - `Timeout document ${timeoutId} has been deleted from user ${userId}.` - ); - }); - } - // Create timeout - await timeoutSchema.create({ - guildId: guild?.id || "0", - userId: member.id, - cooldown: cooldown, - timeoutId: id, - }); -}; diff --git a/src/helpers/dropGuild/index.ts b/src/helpers/dropGuild/index.ts deleted file mode 100644 index 0c0e2ab..0000000 --- a/src/helpers/dropGuild/index.ts +++ /dev/null @@ -1,72 +0,0 @@ -import guildSchema from "../../models/guild"; -import userSchema from "../../models/user"; -import apiSchema from "../../models/api"; -import counterSchema from "../../models/counter"; -import shopRoleSchema from "../../models/shopRole"; -import timeoutSchema from "../../models/timeout"; - -import logger from "../../logger"; - -import { Guild } from "discord.js"; - -export default async (guild: Guild) => { - await guildSchema - .deleteMany({ guildId: guild.id }) - .then(async () => { - return logger?.silly(`Deleted guild: ${guild.id}`); - }) - .catch(async (error) => { - logger?.error(`Error deleting guild: ${guild.id} - ${error}`); - }); - - await userSchema - .deleteMany({ guildId: guild.id }) - .then(async () => { - logger?.silly(`Deleted users for guild: ${guild.id} from database`); - }) - .catch(async (error) => { - logger?.error(`Error deleting users for guild: ${guild.id} - ${error}`); - }); - - await apiSchema - .deleteMany({ guildId: guild.id }) - .then(async () => { - logger?.silly(`Deleted apis for guild: ${guild.id} from database`); - }) - .catch(async (error) => { - logger?.error(`Error deleting apis for guild: ${guild.id} - ${error}`); - }); - - await counterSchema - .deleteMany({ guildId: guild.id }) - .then(async () => { - logger?.silly(`Deleted counters for guild: ${guild.id} from database`); - }) - .catch(async (error) => { - logger?.error( - `Error deleting counters for guild: ${guild.id} - ${error}` - ); - }); - - await shopRoleSchema - .deleteMany({ guildId: guild.id }) - .then(async () => { - logger?.silly(`Deleted shop roles for guild: ${guild.id} from database`); - }) - .catch(async (error) => { - logger?.error( - `Error deleting shop roles for guild: ${guild.id} - ${error}` - ); - }); - - await timeoutSchema - .deleteMany({ guildId: guild.id }) - .then(async () => { - logger?.silly(`Deleted timeouts for guild: ${guild.id} from database`); - }) - .catch(async (error) => { - logger?.error( - `Error deleting timeouts for guild: ${guild.id} - ${error}` - ); - }); -}; diff --git a/src/helpers/dropUser/index.ts b/src/helpers/dropUser/index.ts deleted file mode 100644 index 3093048..0000000 --- a/src/helpers/dropUser/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import userSchema from "../../models/user"; - -import logger from "../../logger"; - -import { Guild, User } from "discord.js"; - -export default async (user: User, guild: Guild) => { - await userSchema - .deleteOne({ userId: user.id, guildId: guild.id }) - .then(async () => { - logger?.silly(`Deleted user: ${user?.id} from guild: ${guild?.id}`); - }) - .catch(async (error) => { - logger?.error( - `Error deleting user: ${user?.id} from guild: ${guild?.id} - ${error}` - ); - }); -}; diff --git a/src/helpers/embedBuilder/index.ts b/src/helpers/embedBuilder/index.ts deleted file mode 100644 index d93cc5c..0000000 --- a/src/helpers/embedBuilder/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { footerText, footerIcon } from "../../config/embed"; -import { MessageEmbed } from "discord.js"; - -export default new MessageEmbed() - .setFooter({ - text: footerText, - iconURL: footerIcon, - }) - .setTimestamp(new Date()); diff --git a/src/handlers/encryption/index.ts b/src/helpers/encryption/index.ts similarity index 74% rename from src/handlers/encryption/index.ts rename to src/helpers/encryption/index.ts index 8b6512c..1b9fc34 100644 --- a/src/handlers/encryption/index.ts +++ b/src/helpers/encryption/index.ts @@ -1,13 +1,15 @@ import crypto from "crypto"; - -import { secretKey, algorithm } from "../../config/encryption"; - import { IEncryptionData } from "../../interfaces/EncryptionData"; const iv = crypto.randomBytes(16); +// Encrypts a string const encrypt = (text: crypto.BinaryLike): IEncryptionData => { - const cipher = crypto.createCipheriv(algorithm, secretKey, iv); + const cipher = crypto.createCipheriv( + process.env.ENCRYPTION_ALGORITHM, + process.env.ENCRYPTION_SECRET, + iv + ); const encrypted = Buffer.concat([cipher.update(text), cipher.final()]); return { @@ -16,10 +18,11 @@ const encrypt = (text: crypto.BinaryLike): IEncryptionData => { }; }; +// Decrypts a string const decrypt = (hash: IEncryptionData) => { const decipher = crypto.createDecipheriv( - algorithm, - secretKey, + process.env.ENCRYPTION_ALGORITHM, + process.env.ENCRYPTION_SECRET, Buffer.from(hash.iv, "hex") ); diff --git a/src/helpers/fetchGuild/index.ts b/src/helpers/fetchGuild/index.ts deleted file mode 100644 index a89e840..0000000 --- a/src/helpers/fetchGuild/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Dependencies -import { Guild } from "discord.js"; - -// Models -import guildSchema from "../../models/guild"; - -// Handlers -import logger from "../../logger"; - -// Function -export default async (guild: Guild) => { - const guildObj = await guildSchema?.findOne({ guildId: guild.id }); - if (guildObj === null) { - const newGuildObj = new guildSchema({ guildId: guild.id }); - - await newGuildObj - .save() - .then(async () => { - logger?.silly(`Created guild: ${guild.id}`); - }) - .catch(async (error) => { - logger?.error(`Error creating guild: ${guild.id} - ${error}`); - }); - - return newGuildObj; - } else { - return guildObj; - } -}; diff --git a/src/helpers/fetchUser/index.ts b/src/helpers/fetchUser/index.ts deleted file mode 100644 index 3837f24..0000000 --- a/src/helpers/fetchUser/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Dependencies -import { Guild, User } from "discord.js"; - -// Models -import userSchema from "../../models/user"; - -// Handlers -import logger from "../../logger"; - -// Function -export default async (user: User, guild: Guild) => { - const userObj = await userSchema?.findOne({ - userId: user.id, - guildId: guild.id, - }); - if (userObj === null) { - const newUserObj = new userSchema({ - userId: user.id, - guildId: guild.id, - }); - - await newUserObj - .save() - .then(async () => { - logger?.silly(`Created user: ${user.id} for guild: ${guild.id}`); - }) - .catch(async (error) => { - logger?.error( - `Error creating user: ${user.id} for guild: ${guild.id} - ${error}` - ); - }); - - return newUserObj; - } else { - return userObj; - } -}; diff --git a/src/helpers/getCommandMetadata/index.ts b/src/helpers/getCommandMetadata/index.ts deleted file mode 100644 index 647eb75..0000000 --- a/src/helpers/getCommandMetadata/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { CommandInteraction } from "discord.js"; -import { ICommand } from "../../interfaces/Command"; - -export default async ( - interaction: CommandInteraction, - currentCommand: ICommand -) => { - const subcommand = interaction.options.getSubcommand(); - const subcommandGroup = interaction.options.getSubcommandGroup(false); - - return subcommandGroup - ? currentCommand.moduleData[subcommandGroup].moduleData[subcommand].metadata - : currentCommand.moduleData[subcommand].metadata; -}; diff --git a/src/helpers/getEmbedConfig/index.ts b/src/helpers/getEmbedConfig/index.ts deleted file mode 100644 index ccd3fdb..0000000 --- a/src/helpers/getEmbedConfig/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import guildSchema from "../../models/guild"; -import * as embedConfig from "../../config/embed"; - -import { Guild } from "discord.js"; - -export default async (guild?: Guild | null) => { - if (!guild) { - return { ...embedConfig }; - } - - const guildConfig = await guildSchema.findOne({ guildId: guild.id }); - if (!guildConfig) { - return { - ...embedConfig, - }; - } - return guildConfig.embeds; -}; diff --git a/src/helpers/getEmbedData/index.ts b/src/helpers/getEmbedData/index.ts new file mode 100644 index 0000000..069ce9b --- /dev/null +++ b/src/helpers/getEmbedData/index.ts @@ -0,0 +1,75 @@ +import { ColorResolvable, Guild } from "discord.js"; +import prisma from "../../handlers/database"; +import logger from "../../middlewares/logger"; + +export default async (guild?: Guild | null) => { + const { + EMBED_COLOR_SUCCESS, + EMBED_COLOR_WAIT, + EMBED_COLOR_ERROR, + EMBED_FOOTER_TEXT, + EMBED_FOOTER_ICON, + } = process.env; + + const defaultEmbedConfig = { + successColor: EMBED_COLOR_SUCCESS, + waitColor: EMBED_COLOR_WAIT, + errorColor: EMBED_COLOR_ERROR, + footerText: EMBED_FOOTER_TEXT, + footerIcon: EMBED_FOOTER_ICON, + }; + + if (!guild) { + return defaultEmbedConfig; + } + + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: guild?.ownerId, + guildId: guild.id, + }, + }, + update: {}, + create: { + user: { + connectOrCreate: { + create: { + id: guild.ownerId, + }, + where: { + id: guild.ownerId, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, + include: { + user: true, + guild: true, + }, + }); + + logger.silly(createGuildMember); + + if (!createGuildMember) { + return defaultEmbedConfig; + } + + return { + successColor: createGuildMember.guild.embedColorSuccess, + waitColor: createGuildMember.guild.embedColorWait, + errorColor: createGuildMember.guild.embedColorError, + footerText: createGuildMember.guild.embedFooterText, + footerIcon: createGuildMember.guild.embedFooterIcon, + }; +}; diff --git a/src/helpers/listDir/index.ts b/src/helpers/listDir/index.ts deleted file mode 100644 index 382e41d..0000000 --- a/src/helpers/listDir/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import fs from "fs"; -const fsPromises = fs.promises; - -export default async (path: string) => { - return fsPromises.readdir(path).catch(async (err) => { - throw new Error(`Could not list directory: ${path}`, err); - }); -}; diff --git a/src/helpers/pluralize/index.ts b/src/helpers/pluralize/index.ts index d6e266d..10a6796 100644 --- a/src/helpers/pluralize/index.ts +++ b/src/helpers/pluralize/index.ts @@ -1,4 +1,4 @@ -import logger from "../../logger"; +import logger from "../../middlewares/logger"; export default (count: number, noun: string, suffix?: string): string => { const result = `${count} ${noun}${count !== 1 ? suffix || "s" : ""}`; diff --git a/src/helpers/transferCredits/index.ts b/src/helpers/transferCredits/index.ts new file mode 100644 index 0000000..46dcf8e --- /dev/null +++ b/src/helpers/transferCredits/index.ts @@ -0,0 +1,106 @@ +import { Guild, User } from "discord.js"; +import prisma from "../../handlers/database"; + +export default async (guild: Guild, from: User, to: User, amount: number) => { + return await prisma.$transaction(async (tx) => { + // 1. Decrement amount from the sender. + const sender = await tx.guildMember.upsert({ + update: { + creditsEarned: { + decrement: amount, + }, + }, + create: { + user: { + connectOrCreate: { + create: { + id: from.id, + }, + where: { + id: from.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + creditsEarned: -amount, + }, + where: { + userId_guildId: { + userId: from.id, + guildId: guild.id, + }, + }, + }); + + // 2. Verify that the sender actually is created. + if (!sender) throw new Error("No sender available"); + + // 3. Verify that the sender's balance exists. + if (!sender.creditsEarned) throw new Error("No credits available"); + + // 4. Verify that the sender's balance didn't go below zero. + if (sender.creditsEarned < 0) { + throw new Error(`${from} doesn't have enough to send ${amount}`); + } + + // 5. Verify that the sender is not trying to send less that one credits. + if (amount <= 0) { + throw new Error("You can't transfer below one credit."); + } + + // 6. Verify that recipient are not an bot. + if (to.bot) throw new Error("You can't transfer to an bot."); + + // 7. Verify that sender and recipient are not the same user. + if (from.id === to.id) throw new Error("You can't transfer to yourself."); + + // 8. Increment the recipient's balance by amount. + const recipient = await tx.guildMember.upsert({ + update: { + creditsEarned: { + increment: amount, + }, + }, + create: { + user: { + connectOrCreate: { + create: { + id: to.id, + }, + where: { + id: to.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + creditsEarned: amount, + }, + where: { + userId_guildId: { + userId: to.id, + guildId: guild.id, + }, + }, + }); + + return recipient; + }); +}; diff --git a/src/index.ts b/src/index.ts index 4327353..b6d54c9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,20 +1,32 @@ -import { token, intents } from "./config/discord"; - -import { Client } from "discord.js"; // discord.js - -import * as managers from "./managers"; +import { Client, Collection, GatewayIntentBits } from "discord.js"; // discord.js +import "dotenv/config"; +import { register as commandRegister } from "./handlers/command"; +import { register as eventRegister } from "./handlers/event"; +import { start as scheduleStart } from "./handlers/schedule"; // Main process that starts all other sub processes const main = async () => { // Initiate client object const client = new Client({ - intents, + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMembers, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + ], }); - await managers.start(client); + // Create command collection + client.commands = new Collection(); + + // Start critical handlers + await scheduleStart(client); + await eventRegister(client); + await commandRegister(client); // Authorize with Discord's API - await client.login(token); + await client.login(process.env.DISCORD_TOKEN); }; +// Start main process main(); diff --git a/src/interfaces/Command.ts b/src/interfaces/Command.ts index f8a895a..5d6f870 100644 --- a/src/interfaces/Command.ts +++ b/src/interfaces/Command.ts @@ -2,6 +2,5 @@ import { SlashCommandBuilder } from "@discordjs/builders"; export interface ICommand { builder: SlashCommandBuilder; - moduleData: any; execute: Promise; } diff --git a/src/jobs/shop/modules/roles/components/dueForPayment.ts b/src/jobs/shop/modules/roles/components/dueForPayment.ts deleted file mode 100644 index ad8a59c..0000000 --- a/src/jobs/shop/modules/roles/components/dueForPayment.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Client } from "discord.js"; -import logger from "../../../../../logger"; - -import { IShopRole } from "../../../../../interfaces/ShopRole"; - -export const execute = async (_client: Client, role: IShopRole) => { - const { roleId } = role; - - logger.silly(`Shop role ${roleId} is not due for payment.`); -}; diff --git a/src/jobs/shop/modules/roles/components/overDueForPayment.ts b/src/jobs/shop/modules/roles/components/overDueForPayment.ts deleted file mode 100644 index 77ab6e0..0000000 --- a/src/jobs/shop/modules/roles/components/overDueForPayment.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Client } from "discord.js"; -import logger from "../../../../../logger"; - -import { IShopRole } from "../../../../../interfaces/ShopRole"; -import guildSchema from "../../../../../models/guild"; -import userSchema from "../../../../../models/user"; -import shopRoleSchema from "../../../../../models/shopRole"; - -export const execute = async (client: Client, role: IShopRole) => { - const { guildId, userId, roleId } = role; - if (!userId) throw new Error("User ID not found for shop role."); - - const guildData = await guildSchema.findOne({ guildId }); - if (!guildData) throw new Error("Guild not found."); - - const userData = await userSchema.findOne({ guildId, userId }); - if (!userData) throw new Error("User not found."); - - const rGuild = client.guilds.cache.get(guildId); - if (!rGuild) throw new Error("Guild not found."); - - const rMember = await rGuild.members.fetch(userId); - if (!rMember) throw new Error("Member not found."); - - const rRole = rMember.roles.cache.get(roleId); - if (!rRole) throw new Error("Role not found."); - - logger.debug(`Shop role ${roleId} is due for payment.`); - - const { pricePerHour } = guildData.shop.roles; - - if (userData.credits < pricePerHour) { - await rMember.roles - .remove(roleId) - .then(async () => { - await shopRoleSchema - .deleteOne({ - userId, - roleId, - guildId, - }) - .then(async () => { - logger.silly( - `Shop role document ${roleId} has been deleted from user ${userId}.` - ); - }) - .catch(async (err) => { - throw new Error( - `Error deleting shop role document ${roleId} from user ${userId}.`, - err - ); - }); - }) - .catch(async (err) => { - throw new Error( - `Error removing role ${roleId} from user ${userId}.`, - err - ); - }); - - throw new Error("User does not have enough credits."); - } - - userData.credits -= pricePerHour; - await userData - .save() - .then(async () => { - logger.silly(`User ${userId} has been updated.`); - - role.lastPayed = new Date(); - await role - .save() - .then(async () => { - logger.silly(`Shop role ${roleId} has been updated.`); - }) - .catch(async (err) => { - throw new Error(`Error updating shop role ${roleId}.`, err); - }); - - logger.debug(`Shop role ${roleId} has been paid.`); - }) - .catch(async (err) => { - throw new Error(`Error updating user ${userId}.`, err); - }); -}; diff --git a/src/jobs/shop/modules/roles/index.ts b/src/jobs/shop/modules/roles/index.ts deleted file mode 100644 index c8e393a..0000000 --- a/src/jobs/shop/modules/roles/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Client } from "discord.js"; - -import { IShopRole } from "../../../../interfaces/ShopRole"; -import shopRoleSchema from "../../../../models/shopRole"; - -import * as overDueForPayment from "./components/overDueForPayment"; -import * as dueForPayment from "./components/dueForPayment"; - -export const execute = async (client: Client) => { - const roles = await shopRoleSchema.find(); - - await Promise.all( - roles.map(async (role: IShopRole) => { - const { lastPayed } = role; - const nextPayment = new Date( - lastPayed.setHours(lastPayed.getHours() + 1) - ); - - const now = new Date(); - - if (nextPayment > now) { - await dueForPayment.execute(client, role); - - return; - } - - if (nextPayment < now) { - await overDueForPayment.execute(client, role); - } - }) - ); -}; diff --git a/src/jobs/timeouts/index.ts b/src/jobs/timeouts/index.ts deleted file mode 100644 index ce18f09..0000000 --- a/src/jobs/timeouts/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import logger from "../../logger"; - -import timeoutSchema from "../../models/timeout"; - -import addSeconds from "../../helpers/addSeconds"; - -export const options = { - schedule: "*/30 * * * *", // https://crontab.guru/ -}; - -export const execute = async () => { - const timeouts = await timeoutSchema.find(); - await Promise.all( - timeouts.map(async (timeout) => { - const { guildId, userId, timeoutId, cooldown, createdAt } = timeout; - - const overDue = (await addSeconds(cooldown, createdAt)) < new Date(); - - if (overDue) { - timeoutSchema - .deleteOne({ - guildId, - userId, - timeoutId, - cooldown, - }) - .then(async () => { - logger.debug( - `Timeout document ${timeoutId} has been deleted from user ${userId}.` - ); - }); - } - }) - ); -}; diff --git a/src/managers/command/index.ts b/src/managers/command/index.ts deleted file mode 100644 index c3c08f9..0000000 --- a/src/managers/command/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Collection, Client } from "discord.js"; -import listDir from "../../helpers/listDir"; -import logger from "../../logger"; - -import { ICommand } from "../../interfaces/Command"; - -export const register = async (client: Client) => { - client.commands = new Collection(); - - const commandNames = await listDir("plugins/commands"); - - if (!commandNames) throw new Error("Could not list commands"); - - logger.info(`Loading ${commandNames.length} commands`); - - await Promise.all( - commandNames.map(async (commandName) => { - const command: ICommand = await import( - `../../plugins/commands/${commandName}` - ).catch(async (e) => { - throw new Error(`Could not load command: ${commandName}`, e); - }); - - client.commands.set(command.builder.name, command); - - logger.verbose(`${command.builder.name} loaded`); - }) - ) - .then(async () => { - logger.info(`Finished loading commands.`); - }) - .catch(async (err) => { - throw new Error(`Could not load commands: ${err}`); - }); -}; diff --git a/src/managers/database/index.ts b/src/managers/database/index.ts deleted file mode 100644 index 0063c61..0000000 --- a/src/managers/database/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -// 3rd party dependencies -import mongoose from "mongoose"; - -// Dependencies -import logger from "../../logger"; - -// Configuration -import { url } from "../../config/database"; - -export const start = async () => { - await mongoose - .connect(url) - .then(async (connection) => { - logger.info(`Connected to database: ${connection.connection.name}`); - }) - .catch(async (e) => { - logger.error("Could not connect to database", e); - }); - - mongoose.connection.on("error", async (error) => { - logger.error(`${error}`); - }); - - mongoose.connection.on("warn", async (warning) => { - logger.warn(warning); - }); -}; diff --git a/src/managers/event/index.ts b/src/managers/event/index.ts deleted file mode 100644 index 348e138..0000000 --- a/src/managers/event/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* eslint-disable no-loops/no-loops */ -import { Client } from "discord.js"; -import listDir from "../../helpers/listDir"; -import { IEvent } from "../../interfaces/Event"; -import logger from "../../logger"; - -export const register = async (client: Client) => { - const eventNames = await listDir("plugins/events"); - if (!eventNames) return; - - for await (const eventName of eventNames) { - const event: IEvent = await import(`../../plugins/events/${eventName}`); - const eventExecutor = async (...args: Promise[]) => - event.execute(...args).catch(async (err) => { - logger.error(`${err}`); - }); - if (!event.options?.type) return; - - switch (event.options.type) { - case "once": - client.once(eventName, eventExecutor); - break; - - case "on": - client.on(eventName, eventExecutor); - break; - } - } -}; diff --git a/src/managers/index.ts b/src/managers/index.ts deleted file mode 100644 index 6981d3c..0000000 --- a/src/managers/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Client } from "discord.js"; - -import * as database from "./database"; -import * as schedule from "./schedule"; -import * as event from "./event"; -import * as command from "./command"; - -export const start = async (client: Client) => { - await database.start(); - await schedule.start(client); - await command.register(client); - await event.register(client); -}; diff --git a/src/managers/schedule/index.ts b/src/managers/schedule/index.ts deleted file mode 100644 index 9fc8aa9..0000000 --- a/src/managers/schedule/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -import logger from "../../logger"; -import { Client } from "discord.js"; - -import { IJob } from "../../interfaces/Job"; - -import listDir from "../../helpers/listDir"; - -import schedule from "node-schedule"; - -export const start = async (client: Client) => { - logger.info("Starting schedule manager..."); - - const jobNames = await listDir("jobs"); - if (!jobNames) return logger.info("No jobs found"); - - await Promise.all( - jobNames.map(async (jobName) => { - const job: IJob = await import(`../../jobs/${jobName}`); - - schedule.scheduleJob(job.options.schedule, async () => { - logger.info(`Executed job ${jobName}!`); - await job.execute(client); - }); - }) - ).then(async () => { - const list = schedule.scheduledJobs; - logger.silly(list); - }); -}; diff --git a/src/logger/index.ts b/src/middlewares/logger/index.ts similarity index 91% rename from src/logger/index.ts rename to src/middlewares/logger/index.ts index 0593ec9..a91f01d 100644 --- a/src/logger/index.ts +++ b/src/middlewares/logger/index.ts @@ -1,13 +1,11 @@ import winston from "winston"; import "winston-daily-rotate-file"; -import { logLevel } from "../config/other"; - const { combine, timestamp, printf, errors, colorize, align, json } = winston.format; export default winston.createLogger({ - level: logLevel || "info", + level: process.env.LOG_LEVEL || "info", transports: [ new winston.transports.DailyRotateFile({ filename: "logs/combined-%DATE%.log", diff --git a/src/models/api.ts b/src/models/api.ts deleted file mode 100644 index fd5cbd7..0000000 --- a/src/models/api.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Snowflake } from "discord.js"; -import { model, Schema } from "mongoose"; -import { IEncryptionData } from "../interfaces/EncryptionData"; - -export interface IApi { - guildId: Snowflake; - url: IEncryptionData; - token: IEncryptionData; -} - -const apiSchema = new Schema( - { - guildId: { - type: String, - required: true, - unique: false, - index: true, - }, - url: { - iv: { - type: String, - required: true, - unique: false, - index: true, - }, - content: { - type: String, - required: true, - unique: false, - index: true, - }, - }, - token: { - iv: { - type: String, - required: true, - unique: false, - index: true, - }, - content: { - type: String, - required: true, - unique: false, - index: true, - }, - }, - }, - { timestamps: true } -); - -export default model("api", apiSchema); diff --git a/src/models/counter.ts b/src/models/counter.ts deleted file mode 100644 index fd274eb..0000000 --- a/src/models/counter.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Schema, model } from "mongoose"; -import { Snowflake } from "discord.js"; - -export interface ICounter { - guildId: Snowflake; - channelId: Snowflake; - word: string; - counter: number; -} - -const counterSchema = new Schema( - { - guildId: { - type: String, - required: true, - unique: false, - index: true, - }, - channelId: { - type: String, - required: true, - unique: true, - index: true, - }, - word: { - type: String, - required: true, - unique: false, - index: true, - }, - counter: { - type: Number, - required: true, - unique: false, - index: true, - default: 0, - }, - }, - { timestamps: true } -); - -export default model("counter", counterSchema); diff --git a/src/models/guild.ts b/src/models/guild.ts deleted file mode 100644 index 0b7a78d..0000000 --- a/src/models/guild.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { ColorResolvable } from "discord.js"; -import { Schema, model } from "mongoose"; - -interface IGuild { - guildId: string; - credits: { - status: boolean; - rate: number; - timeout: number; - workRate: number; - minimumLength: number; - workTimeout: number; - }; - embeds: { - successColor: ColorResolvable; - waitColor: ColorResolvable; - errorColor: ColorResolvable; - footerIcon: string; - footerText: string; - }; - shop: { roles: { status: boolean; pricePerHour: number } }; - points: { - status: boolean; - rate: number; - minimumLength: number; - timeout: number; - }; - welcome: { - status: boolean; - joinChannel: string; - leaveChannel: string; - joinChannelMessage: string; - leaveChannelMessage: string; - }; - audits: { status: boolean; channelId: string }; -} - -const guildSchema = new Schema( - { - guildId: { - type: String, - required: true, - unique: true, - index: true, - }, - credits: { - status: { - type: Boolean, - default: true, - }, - rate: { - type: Number, - default: 1, - }, - minimumLength: { - type: Number, - default: 5, - }, - timeout: { - type: Number, - default: 5000, - }, - workRate: { - type: Number, - default: 15, - }, - workTimeout: { - type: Number, - default: 900000, - }, - }, - embeds: { - successColor: { - type: String, - default: "#22bb33", - }, - waitColor: { - type: String, - default: "#f0ad4e", - }, - errorColor: { - type: String, - default: "#bb2124", - }, - footerText: { - type: String, - default: "https://github.com/ZynerOrg/xyter", - }, - footerIcon: { - type: String, - default: "https://github.com/ZynerOrg.png", - }, - }, - shop: { - roles: { - status: { - type: Boolean, - default: false, - }, - pricePerHour: { - type: Number, - default: 5, - }, - }, - }, - points: { - status: { - type: Boolean, - default: false, - }, - rate: { - type: Number, - default: 1, - }, - minimumLength: { - type: Number, - default: 5, - }, - timeout: { - type: Number, - default: 5000, - }, - }, - welcome: { - status: { - type: Boolean, - default: false, - }, - joinChannel: { type: String }, - leaveChannel: { type: String }, - joinChannelMessage: { type: String }, - leaveChannelMessage: { type: String }, - }, - audits: { - status: { type: Boolean, default: false }, - channelId: { type: String }, - }, - }, - { timestamps: true } -); - -export default model("guild", guildSchema); diff --git a/src/models/shopRole.ts b/src/models/shopRole.ts deleted file mode 100644 index 19692fa..0000000 --- a/src/models/shopRole.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Snowflake } from "discord.js"; -import { Schema, model } from "mongoose"; - -export interface IShopRole { - roleId: Snowflake; - userId: Snowflake; - guildId: Snowflake; - pricePerHour: number; - lastPayed: Date; -} - -const shopRoleSchema = new Schema( - { - roleId: { - type: String, - required: true, - unique: false, - index: true, - }, - userId: { - type: String, - required: true, - unique: false, - index: true, - }, - guildId: { - type: String, - required: true, - unique: false, - index: true, - }, - pricePerHour: { - type: Number, - required: true, - unique: false, - index: true, - default: 5, - }, - lastPayed: { - type: Date, - unique: false, - index: true, - }, - }, - { timestamps: true } -); - -export default model("shopRole", shopRoleSchema); diff --git a/src/models/timeout.ts b/src/models/timeout.ts deleted file mode 100644 index 0e3554c..0000000 --- a/src/models/timeout.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Snowflake } from "discord.js"; -import { Schema, model } from "mongoose"; - -export interface ITimeout { - userId: Snowflake; - guildId: Snowflake; - cooldown: number; - timeoutId: string; - createdAt: Date; - updatedAt: Date; -} - -const timeoutSchema = new Schema( - { - userId: { - type: String, - required: true, - unique: false, - index: true, - }, - guildId: { - type: String, - required: true, - unique: false, - index: true, - }, - cooldown: { - type: Number, - required: true, - unique: false, - index: true, - }, - timeoutId: { type: String }, - }, - { timestamps: true } -); - -export default model("timeout", timeoutSchema); diff --git a/src/models/user.ts b/src/models/user.ts deleted file mode 100644 index 387ac7f..0000000 --- a/src/models/user.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Snowflake } from "discord.js"; -import { Schema, model } from "mongoose"; - -export interface IUser { - guildId: Snowflake; - userId: Snowflake; - language: string; - reputation: number; - credits: number; - level: number; - points: number; - updatedAt: Date; - createdAt: Date; -} - -const userSchema = new Schema( - { - guildId: { - type: String, - required: true, - unique: false, - index: true, - }, - userId: { - type: String, - required: true, - unique: false, - index: true, - }, - language: { - type: String, - default: "en", - }, - reputation: { type: Number, default: 0 }, - credits: { type: Number, default: 0 }, - level: { type: Number, default: 0 }, - points: { type: Number, default: 0 }, - }, - { timestamps: true } -); - -export default model("user", userSchema); diff --git a/src/plugins/commands/config/index.ts b/src/plugins/commands/config/index.ts deleted file mode 100644 index 3e8b585..0000000 --- a/src/plugins/commands/config/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -import modules from "./modules"; - -export const builder = new SlashCommandBuilder() - .setName("config") - .setDescription("Manage guild configurations.") - - .addSubcommand(modules.cpgg.builder) - .addSubcommand(modules.credits.builder) - .addSubcommand(modules.points.builder) - .addSubcommand(modules.welcome.builder) - .addSubcommand(modules.audits.builder) - .addSubcommand(modules.shop.builder) - .addSubcommand(modules.embeds.builder); - -export const moduleData = modules; - -export const execute = async (interaction: CommandInteraction) => { - switch (interaction.options?.getSubcommand()) { - case "cpgg": - return modules.cpgg.execute(interaction); - case "credits": - return modules.credits.execute(interaction); - case "points": - return modules.points.execute(interaction); - case "welcome": - return modules.welcome.execute(interaction); - case "audits": - return modules.audits.execute(interaction); - case "shop": - return modules.shop.execute(interaction); - case "embeds": - return modules.embeds.execute(interaction); - } -}; diff --git a/src/plugins/commands/config/modules/audits/index.ts b/src/plugins/commands/config/modules/audits/index.ts deleted file mode 100644 index f090b45..0000000 --- a/src/plugins/commands/config/modules/audits/index.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { CommandInteraction, Permissions } from "discord.js"; - -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - -import logger from "../../../../../logger"; - -import guildSchema from "../../../../../models/guild"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import { ChannelType } from "discord-api-types/v10"; - -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("audits") - .setDescription("Audits") - .addBooleanOption((option) => - option.setName("status").setDescription("Should audits be enabled?") - ) - .addChannelOption((option) => - option - .setName("channel") - .setDescription("Channel for audit messages.") - .addChannelTypes(ChannelType.GuildText) - ); - }, - execute: async (interaction: CommandInteraction) => { - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - - const { guild, options } = interaction; - - const status = options?.getBoolean("status"); - const channel = options?.getChannel("channel"); - - const guildDB = await guildSchema?.findOne({ - guildId: guild?.id, - }); - - if (guildDB === null) { - return logger?.silly(`Guild not found in database.`); - } - - guildDB.audits.status = status !== null ? status : guildDB?.audits?.status; - guildDB.audits.channelId = - channel !== null ? channel.id : guildDB?.audits?.channelId; - - await guildDB?.save()?.then(async () => { - logger?.silly(`Guild audits updated.`); - - return interaction?.editReply({ - embeds: [ - { - title: ":hammer: Settings - Guild [Audits]", - description: `Audits settings updated.`, - color: successColor, - fields: [ - { - name: "🤖 Status", - value: `${guildDB?.audits?.status}`, - inline: true, - }, - { - name: "🌊 Channel", - value: `${guildDB?.audits?.channelId}`, - inline: true, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - }, -}; diff --git a/src/plugins/commands/config/modules/cpgg/index.ts b/src/plugins/commands/config/modules/cpgg/index.ts deleted file mode 100644 index 4ba34e0..0000000 --- a/src/plugins/commands/config/modules/cpgg/index.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { CommandInteraction, Permissions } from "discord.js"; - -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - -import logger from "../../../../../logger"; - -import apiSchema from "../../../../../models/api"; -import encryption from "../../../../../handlers/encryption"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("cpgg") - .setDescription("Controlpanel.gg") - .addStringOption((option) => - option - .setName("scheme") - .setDescription(`Controlpanel.gg Scheme`) - .setRequired(true) - .setChoices( - { name: "HTTPS (secure)", value: "https" }, - { name: "HTTP (insecure)", value: "http" } - ) - ) - .addStringOption((option) => - option - .setName("domain") - .setDescription(`Controlpanel.gg Domain`) - .setRequired(true) - ) - .addStringOption((option) => - option - .setName("token") - .setDescription(`Controlpanel.gg Application API`) - .setRequired(true) - ); - }, - execute: async (interaction: CommandInteraction) => { - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - const { options, guild } = interaction; - - const tokenData = options.getString("token"); - const scheme = options.getString("scheme"); - const domain = options.getString("domain"); - const token = tokenData && encryption.encrypt(tokenData); - const url = scheme && domain && encryption.encrypt(`${scheme}://${domain}`); - - await apiSchema - ?.findOneAndUpdate( - { guildId: guild?.id }, - { url, token }, - { new: true, upsert: true } - ) - .then(async () => { - logger?.silly(`Updated API credentials.`); - - return interaction?.editReply({ - embeds: [ - { - title: "[:tools:] CPGG", - description: `The following configuration will be used. - - **Scheme**: ${scheme} - **Domain**: ${domain} - **Token**: ends with ${tokenData?.slice(-4)}`, - color: successColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - }, -}; diff --git a/src/plugins/commands/config/modules/credits/index.ts b/src/plugins/commands/config/modules/credits/index.ts deleted file mode 100644 index f8b344f..0000000 --- a/src/plugins/commands/config/modules/credits/index.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { CommandInteraction, Permissions } from "discord.js"; - -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - -import logger from "../../../../../logger"; - -import guildSchema from "../../../../../models/guild"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("credits") - .setDescription(`Credits`) - .addBooleanOption((option) => - option.setName("status").setDescription("Should credits be enabled?") - ) - .addNumberOption((option) => - option.setName("rate").setDescription("Amount of credits per message.") - ) - .addNumberOption((option) => - option - .setName("minimum-length") - .setDescription("Minimum length of message to earn credits.") - ) - .addNumberOption((option) => - option - .setName("work-rate") - .setDescription("Maximum amount of credits on work.") - ) - .addNumberOption((option) => - option - .setName("work-timeout") - .setDescription("Timeout between work schedules (seconds).") - ) - .addNumberOption((option) => - option - .setName("timeout") - .setDescription("Timeout between earning credits (seconds).") - ); - }, - execute: async (interaction: CommandInteraction) => { - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - const { guild, options } = interaction; - - if (guild == null) return; - - const status = options?.getBoolean("status"); - const rate = options?.getNumber("rate"); - const timeout = options?.getNumber("timeout"); - const minimumLength = options?.getNumber("minimum-length"); - const workRate = options?.getNumber("work-rate"); - const workTimeout = options?.getNumber("work-timeout"); - - const guildDB = await guildSchema?.findOne({ - guildId: guild?.id, - }); - - if (guildDB === null) { - return logger?.silly(`Guild is null`); - } - - guildDB.credits.status = - status !== null ? status : guildDB?.credits?.status; - guildDB.credits.rate = rate !== null ? rate : guildDB?.credits?.rate; - guildDB.credits.timeout = - timeout !== null ? timeout : guildDB?.credits?.timeout; - guildDB.credits.workRate = - workRate !== null ? workRate : guildDB?.credits?.workRate; - guildDB.credits.workTimeout = - workTimeout !== null ? workTimeout : guildDB?.credits?.workTimeout; - guildDB.credits.minimumLength = - minimumLength !== null ? minimumLength : guildDB?.credits?.minimumLength; - - await guildDB?.save()?.then(async () => { - logger?.silly(`Guild saved`); - - return interaction?.editReply({ - embeds: [ - { - title: ":tools: Settings - Guild [Credits]", - description: `Credits settings updated.`, - color: successColor, - fields: [ - { - name: "🤖 Status", - value: `${guildDB?.credits?.status}`, - inline: true, - }, - { - name: "📈 Rate", - value: `${guildDB?.credits?.rate}`, - inline: true, - }, - { - name: "📈 Work Rate", - value: `${guildDB?.credits?.workRate}`, - inline: true, - }, - { - name: "🔨 Minimum Length", - value: `${guildDB?.credits?.minimumLength}`, - inline: true, - }, - { - name: "⏰ Timeout", - value: `${guildDB?.credits?.timeout}`, - inline: true, - }, - { - name: "⏰ Work Timeout", - value: `${guildDB?.credits?.workTimeout}`, - inline: true, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - }, -}; diff --git a/src/plugins/commands/config/modules/embeds/components/getValues/index.ts b/src/plugins/commands/config/modules/embeds/components/getValues/index.ts deleted file mode 100644 index a61ecb3..0000000 --- a/src/plugins/commands/config/modules/embeds/components/getValues/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ColorResolvable, CommandInteraction } from "discord.js"; -import guildSchema from "../../../../../../../models/guild"; -import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; - -export default async (interaction: CommandInteraction) => { - const { options, guild } = interaction; - - if (!guild) throw new Error("Guild not found"); - - const embedConfig = await getEmbedConfig(guild); - if (!embedConfig) throw new Error("Embed config not found"); - - const newSuccessColor = options.getString("success-color") as ColorResolvable; - const newWaitColor = options.getString("wait-color") as ColorResolvable; - const newErrorColor = options.getString("error-color") as ColorResolvable; - const newFooterIcon = options.getString("footer-icon"); - const newFooterText = options.getString("footer-text"); - - const guildData = await guildSchema.findOne({ - guildId: guild.id, - }); - if (!guildData) throw new Error("Guild data not found"); - if (!guildData?.embeds) - throw new Error("Guild embed configuration not found"); - let { successColor, waitColor, errorColor, footerText, footerIcon } = - guildData.embeds; - - successColor = newSuccessColor || successColor; - waitColor = newWaitColor || waitColor; - errorColor = newErrorColor || errorColor; - footerIcon = newFooterIcon || footerIcon; - footerText = newFooterText || footerText; - - return { successColor, waitColor, errorColor, footerText, footerIcon }; -}; diff --git a/src/plugins/commands/config/modules/embeds/index.ts b/src/plugins/commands/config/modules/embeds/index.ts deleted file mode 100644 index db8bef0..0000000 --- a/src/plugins/commands/config/modules/embeds/index.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { - ColorResolvable, - CommandInteraction, - MessageEmbed, - Permissions, -} from "discord.js"; - -import logger from "../../../../../logger"; - -import guildSchema from "../../../../../models/guild"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; -import getValues from "./components/getValues"; - -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("embeds") - .setDescription(`Embeds`) - .addStringOption((option) => - option - .setName("success-color") - .setDescription("No provided description") - ) - .addStringOption((option) => - option.setName("wait-color").setDescription("No provided description") - ) - .addStringOption((option) => - option.setName("error-color").setDescription("No provided description") - ) - .addStringOption((option) => - option.setName("footer-icon").setDescription("No provided description") - ) - .addStringOption((option) => - option.setName("footer-text").setDescription("No provided description") - ); - }, - execute: async (interaction: CommandInteraction) => { - const { guild } = interaction; - if (!guild) throw new Error("Guild not found"); - - const { successColor, waitColor, errorColor, footerText, footerIcon } = - await getValues(interaction); - - const embed = new MessageEmbed() - .setTitle("[:tools:] Embeds") - .setFooter({ text: footerText, iconURL: footerIcon }) - .setTimestamp(new Date()); - - const guildData = await guildSchema.findOne({ - guildId: guild.id, - }); - if (!guildData) throw new Error("Guild data not found"); - - await guildData.save().then(async () => { - embed - .setDescription("Following embed configuration will be used.") - .setColor(successColor) - .addFields([ - { - name: "🟢 Success Color", - value: `${successColor}`, - inline: true, - }, - { - name: "🟡 Wait Color", - value: `${waitColor}`, - inline: true, - }, - { - name: "🔴 Error Color", - value: `${errorColor}`, - inline: true, - }, - { - name: "🖼️ Footer Icon", - value: `${footerIcon}`, - inline: true, - }, - { - name: "📄 Footer Text", - value: `${footerText}`, - inline: true, - }, - ]); - - return interaction.editReply({ - embeds: [embed], - }); - }); - }, -}; diff --git a/src/plugins/commands/config/modules/index.ts b/src/plugins/commands/config/modules/index.ts deleted file mode 100644 index 45d8a9f..0000000 --- a/src/plugins/commands/config/modules/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import audits from "./audits"; -import credits from "./credits"; -import points from "./points"; -import cpgg from "./cpgg"; -import shop from "./shop"; -import welcome from "./welcome"; -import embeds from "./embeds"; - -export default { audits, credits, points, cpgg, shop, welcome, embeds }; diff --git a/src/plugins/commands/config/modules/points/index.ts b/src/plugins/commands/config/modules/points/index.ts deleted file mode 100644 index 8813cf6..0000000 --- a/src/plugins/commands/config/modules/points/index.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { CommandInteraction, Permissions } from "discord.js"; - -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - -import logger from "../../../../../logger"; - -import guildSchema from "../../../../../models/guild"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("points") - .setDescription("Points") - .addBooleanOption((option) => - option.setName("status").setDescription("Should credits be enabled?") - ) - .addNumberOption((option) => - option.setName("rate").setDescription("Amount of credits per message.") - ) - .addNumberOption((option) => - option - .setName("minimum-length") - .setDescription("Minimum length of message to earn credits.") - ) - .addNumberOption((option) => - option - .setName("timeout") - .setDescription("Timeout between earning credits (milliseconds).") - ); - }, - execute: async (interaction: CommandInteraction) => { - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - - const { options, guild } = interaction; - - const status = options?.getBoolean("status"); - const rate = options?.getNumber("rate"); - const timeout = options?.getNumber("timeout"); - const minimumLength = options?.getNumber("minimum-length"); - - const guildDB = await guildSchema?.findOne({ - guildId: guild?.id, - }); - - if (guildDB === null) { - return logger?.silly(`Guild not found in database.`); - } - - guildDB.points.status = status !== null ? status : guildDB?.points?.status; - guildDB.points.rate = rate !== null ? rate : guildDB?.points?.rate; - guildDB.points.timeout = - timeout !== null ? timeout : guildDB?.points?.timeout; - guildDB.points.minimumLength = - minimumLength !== null ? minimumLength : guildDB?.points?.minimumLength; - - await guildDB?.save()?.then(async () => { - logger?.silly(`Guild points updated.`); - - return interaction?.editReply({ - embeds: [ - { - title: ":hammer: Settings - Guild [Points]", - description: `Points settings updated.`, - color: successColor, - fields: [ - { - name: "🤖 Status", - value: `${guildDB?.points?.status}`, - inline: true, - }, - { - name: "📈 Rate", - value: `${guildDB?.points?.rate}`, - inline: true, - }, - { - name: "🔨 Minimum Length", - value: `${guildDB?.points?.minimumLength}`, - inline: true, - }, - { - name: "⏰ Timeout", - value: `${guildDB?.points?.timeout}`, - inline: true, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - }, -}; diff --git a/src/plugins/commands/config/modules/shop/index.ts b/src/plugins/commands/config/modules/shop/index.ts deleted file mode 100644 index 328b169..0000000 --- a/src/plugins/commands/config/modules/shop/index.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { CommandInteraction, Permissions } from "discord.js"; - -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - -import logger from "../../../../../logger"; - -import guildSchema from "../../../../../models/guild"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("shop") - .setDescription("Shop") - .addBooleanOption((option) => - option - .setName("roles-status") - .setDescription("Should roles be enabled?") - ) - .addNumberOption((option) => - option - .setName("roles-price-per-hour") - .setDescription("Price per hour for roles.") - ); - }, - execute: async (interaction: CommandInteraction) => { - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - const { options, guild } = interaction; - - const rolesStatus = options?.getBoolean("roles-status"); - const rolesPricePerHour = options?.getNumber("roles-price-per-hour"); - - const guildDB = await guildSchema?.findOne({ - guildId: guild?.id, - }); - - if (guildDB === null) { - return logger?.silly(`Guild not found in database.`); - } - - guildDB.shop.roles.status = - rolesStatus !== null ? rolesStatus : guildDB?.shop?.roles?.status; - guildDB.shop.roles.pricePerHour = - rolesPricePerHour !== null - ? rolesPricePerHour - : guildDB?.shop?.roles?.pricePerHour; - - await guildDB?.save()?.then(async () => { - logger?.silly(`Guild shop updated.`); - - return interaction?.editReply({ - embeds: [ - { - title: ":hammer: Settings - Guild [Shop]", - description: `Shop settings updated.`, - color: successColor, - fields: [ - { - name: "🤖 Roles Status", - value: `${guildDB?.shop?.roles.status}`, - inline: true, - }, - { - name: "🌊 Roles Price Per Hour", - value: `${guildDB?.shop?.roles.pricePerHour}`, - inline: true, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - }, -}; diff --git a/src/plugins/commands/config/modules/welcome/index.ts b/src/plugins/commands/config/modules/welcome/index.ts deleted file mode 100644 index f77dd4e..0000000 --- a/src/plugins/commands/config/modules/welcome/index.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { CommandInteraction, Permissions } from "discord.js"; - -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - -import logger from "../../../../../logger"; - -import guildSchema from "../../../../../models/guild"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import { ChannelType } from "discord-api-types/v10"; - -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("welcome") - .setDescription("Welcome") - .addBooleanOption((option) => - option.setName("status").setDescription("Should welcome be enabled?") - ) - .addChannelOption((option) => - option - .setName("join-channel") - .setDescription("Channel for join messages.") - .addChannelTypes(ChannelType.GuildText) - ) - - .addChannelOption((option) => - option - .setName("leave-channel") - .setDescription("Channel for leave messages.") - .addChannelTypes(ChannelType.GuildText) - ) - - .addStringOption((option) => - option - .setName("leave-message") - .setDescription("Message for leave messages.") - ) - .addStringOption((option) => - option - .setName("join-message") - .setDescription("Message for join messages.") - ); - }, - execute: async (interaction: CommandInteraction) => { - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - const { options, guild } = interaction; - - const status = options?.getBoolean("status"); - const joinChannel = options?.getChannel("join-channel"); - const leaveChannel = options?.getChannel("leave-channel"); - const joinChannelMessage = options?.getString("join-message"); - const leaveChannelMessage = options?.getString("leave-message"); - - const guildDB = await guildSchema?.findOne({ - guildId: guild?.id, - }); - - if (guildDB === null) { - return logger?.silly(`Guild not found in database.`); - } - - guildDB.welcome.status = - status !== null ? status : guildDB?.welcome?.status; - guildDB.welcome.joinChannel = - joinChannel !== null ? joinChannel.id : guildDB?.welcome?.joinChannel; - guildDB.welcome.leaveChannel = - leaveChannel !== null ? leaveChannel.id : guildDB?.welcome?.leaveChannel; - - guildDB.welcome.joinChannelMessage = - joinChannelMessage !== null - ? joinChannelMessage - : guildDB?.welcome?.joinChannelMessage; - guildDB.welcome.leaveChannelMessage = - leaveChannelMessage !== null - ? leaveChannelMessage - : guildDB?.welcome?.leaveChannelMessage; - - await guildDB?.save()?.then(async () => { - logger?.silly(`Guild welcome updated.`); - - if (!guildDB?.welcome?.status) { - return interaction?.editReply({ - embeds: [ - { - title: "[:tools:] Welcome", - description: `This module is currently disabled, please enable it to continue.`, - color: successColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - } - - return interaction?.editReply({ - embeds: [ - { - title: "[:tools:] Welcome", - description: `The following configuration will be used. - - [👋] **Welcome** - - ㅤ**Channel**: <#${guildDB?.welcome?.joinChannel}> - ㅤ**Message**: ${guildDB?.welcome?.joinChannelMessage} - - [🚪] **Leave** - - ㅤ**Channel**: <#${guildDB?.welcome?.leaveChannel}> - ㅤ**Message**: ${guildDB?.welcome?.leaveChannelMessage}`, - color: successColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - }, -}; diff --git a/src/plugins/commands/counters/index.ts b/src/plugins/commands/counters/index.ts deleted file mode 100644 index 9626b99..0000000 --- a/src/plugins/commands/counters/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CommandInteraction } from "discord.js"; -import { SlashCommandBuilder } from "@discordjs/builders"; - -import modules from "../../commands/counters/modules"; - -export const builder = new SlashCommandBuilder() - .setName("counters") - .setDescription("View guild counters") - - .addSubcommand(modules.view.builder); - -export const moduleData = modules; - -export const execute = async (interaction: CommandInteraction) => { - if (interaction.options.getSubcommand() === "view") { - await modules.view.execute(interaction); - } -}; diff --git a/src/plugins/commands/counters/modules/index.ts b/src/plugins/commands/counters/modules/index.ts deleted file mode 100644 index dc539f8..0000000 --- a/src/plugins/commands/counters/modules/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import view from "./view"; - -export default { view }; diff --git a/src/plugins/commands/counters/modules/view/index.ts b/src/plugins/commands/counters/modules/view/index.ts deleted file mode 100644 index 77e1714..0000000 --- a/src/plugins/commands/counters/modules/view/index.ts +++ /dev/null @@ -1,67 +0,0 @@ -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - -import { CommandInteraction, MessageEmbed } from "discord.js"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import { ChannelType } from "discord-api-types/v10"; - -import counterSchema from "../../../../../models/counter"; - -export default { - metadata: { guildOnly: true, ephemeral: false }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("view") - .setDescription(`View a guild counter`) - .addChannelOption((option) => - option - .setName("channel") - .setDescription( - `The channel that contains the counter you want to view` - ) - .setRequired(true) - .addChannelTypes(ChannelType.GuildText) - ); - }, - - execute: async (interaction: CommandInteraction) => { - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); - const { options, guild } = interaction; - - const discordChannel = options?.getChannel("channel"); - - const embed = new MessageEmbed() - .setTitle("[:1234:] Counters (View)") - .setTimestamp(new Date()) - .setFooter({ - text: footerText, - iconURL: footerIcon, - }); - - const counter = await counterSchema?.findOne({ - guildId: guild?.id, - channelId: discordChannel?.id, - }); - - if (counter === null) { - return interaction?.editReply({ - embeds: [ - embed - .setDescription(`No counter found for channel ${discordChannel}!`) - .setColor(errorColor), - ], - }); - } - - return interaction?.editReply({ - embeds: [ - embed - .setDescription( - `Viewing counter for channel ${discordChannel}: ${counter.counter}!` - ) - .setColor(successColor), - ], - }); - }, -}; diff --git a/src/plugins/commands/credits/index.ts b/src/plugins/commands/credits/index.ts deleted file mode 100644 index 9d1a3b4..0000000 --- a/src/plugins/commands/credits/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; -import logger from "../../../logger"; - -import modules from "./modules"; - -export const builder = new SlashCommandBuilder() - .setName("credits") - .setDescription("Manage your credits.") - - .addSubcommand(modules.balance.builder) - .addSubcommand(modules.gift.builder) - .addSubcommand(modules.top.builder) - .addSubcommand(modules.work.builder); - -export const moduleData = modules; - -export const execute = async (interaction: CommandInteraction) => { - const { options } = interaction; - - switch (options.getSubcommand()) { - case "balance": - await modules.balance.execute(interaction); - break; - case "gift": - await modules.gift.execute(interaction); - break; - case "top": - await modules.top.execute(interaction); - break; - case "work": - await modules.work.execute(interaction); - break; - default: - logger.silly(`Unknown subcommand ${options.getSubcommand()}`); - } -}; diff --git a/src/plugins/commands/credits/modules/balance/index.ts b/src/plugins/commands/credits/modules/balance/index.ts deleted file mode 100644 index 0e646c2..0000000 --- a/src/plugins/commands/credits/modules/balance/index.ts +++ /dev/null @@ -1,81 +0,0 @@ -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - -import { CommandInteraction, MessageEmbed } from "discord.js"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import logger from "../../../../../logger"; - -import fetchUser from "../../../../../helpers/fetchUser"; - -export default { - metadata: { guildOnly: true, ephemeral: true }, - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("balance") - .setDescription(`View a user's balance`) - .addUserOption((option) => - option - .setName("user") - .setDescription(`The user whose balance you want to view`) - ); - }, - execute: async (interaction: CommandInteraction) => { - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); - const { options, user, guild } = interaction; - - const discordUser = options.getUser("user"); - - const embed = new MessageEmbed() - .setTitle("[:dollar:] Balance") - .setTimestamp(new Date()) - .setFooter({ text: footerText, iconURL: footerIcon }); - - if (guild === null) { - logger.silly(`Guild is null`); - - return interaction.editReply({ - embeds: [ - embed.setDescription("Guild is not found").setColor(errorColor), - ], - }); - } - - const userObj = await fetchUser(discordUser || user, guild); - - if (userObj === null) { - logger.silly(`User not found`); - - return interaction.editReply({ - embeds: [ - embed - .setDescription( - "User is not found. Please try again with a valid user." - ) - .setColor(errorColor), - ], - }); - } - - if (userObj.credits === null) { - logger.silly(`User has no credits`); - - return interaction.editReply({ - embeds: [ - embed.setDescription("Credits not found").setColor(errorColor), - ], - }); - } - - logger.silly(`Found user ${discordUser || user}`); - - return interaction.editReply({ - embeds: [ - embed - .setDescription( - `${discordUser || user} currently has ${userObj.credits} credits.` - ) - .setColor(successColor), - ], - }); - }, -}; diff --git a/src/plugins/commands/credits/modules/gift/index.ts b/src/plugins/commands/credits/modules/gift/index.ts deleted file mode 100644 index 84b2350..0000000 --- a/src/plugins/commands/credits/modules/gift/index.ts +++ /dev/null @@ -1,250 +0,0 @@ -// Dependencies -import { CommandInteraction, MessageEmbed } from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - -// Handlers -import logger from "../../../../../logger"; - -import mongoose from "mongoose"; - -// Models -import fetchUser from "../../../../../helpers/fetchUser"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -// Function -export default { - metadata: { guildOnly: true, ephemeral: true }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("gift") - .setDescription(`Gift a user credits`) - .addUserOption((option) => - option - .setName("user") - .setDescription("The user you want to gift credits to.") - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName("amount") - .setDescription("The amount of credits you want to gift.") - .setRequired(true) - ) - .addStringOption((option) => - option.setName("reason").setDescription("Your reason.") - ); - }, - execute: async (interaction: CommandInteraction) => { - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); - const { options, user, guild, client } = interaction; - - const optionUser = options.getUser("user"); - const optionAmount = options.getInteger("amount"); - const optionReason = options.getString("reason"); - - const embed = new MessageEmbed() - .setTitle("[:dollar:] Gift") - .setTimestamp(new Date()) - .setFooter({ text: footerText, iconURL: footerIcon }); - - if (guild === null) { - logger.silly(`Guild is null`); - - return interaction.editReply({ - embeds: [ - embed.setDescription("Guild is not found").setColor(errorColor), - ], - }); - } - - if (optionUser === null) { - logger.silly(`User not found`); - - return interaction.editReply({ - embeds: [ - embed - .setDescription(`User is not found in this guild`) - .setColor(errorColor), - ], - }); - } - - // Get fromUserDB object - const fromUserDB = await fetchUser(user, guild); - - // Get toUserDB object - const toUserDB = await fetchUser(optionUser, guild); - - if (fromUserDB === null) { - logger.silly(`User not found`); - - return interaction.editReply({ - embeds: [ - embed - .setDescription( - "You do not have any credits. Please write something in the chat to get some." - ) - .setColor(errorColor), - ], - }); - } - - if (toUserDB === null) { - logger.silly(`User not found`); - - return interaction.editReply({ - embeds: [ - embed - .setDescription( - "The user you want to gift credits to does not have any credits. Please wait until that user has typed something in the chat to get some." - ) - .setColor(errorColor), - ], - }); - } - - // If receiver is same as sender - if (optionUser.id === user.id) { - logger.silly(`User is same as sender`); - - return interaction.editReply({ - embeds: [ - embed - .setDescription( - "You can't gift credits to yourself. Please choose a different user." - ) - .setColor(errorColor), - ], - }); - } - - // If amount is null - if (optionAmount === null) { - logger.silly(`Amount is null`); - - return interaction.editReply({ - embeds: [ - embed - .setDescription( - "Please specify the amount of credits you want to gift." - ) - .setColor(errorColor), - ], - }); - } - - // If amount is zero or below - if (optionAmount <= 0) { - logger.silly(`Amount is zero or below`); - - return interaction.editReply({ - embeds: [ - embed - .setDescription( - "Please specify a valid amount of credits you want to gift." - ) - .setColor(errorColor), - ], - }); - } - - // If user has below gifting amount - if (fromUserDB.credits < optionAmount) { - logger.silly(`User has below gifting amount`); - - return interaction.editReply({ - embeds: [ - embed - .setDescription( - "You don't have enough credits to gift that amount. Please try again with a lower amount." - ) - .setColor(errorColor), - ], - }); - } - - // If toUserDB has no credits - if (toUserDB === null) { - logger.silly(`User has no credits`); - - return interaction.editReply({ - embeds: [ - embed - .setDescription( - "The user you want to gift credits to does not have any credits. Please wait until that user has typed something in the chat to get some." - ) - .setColor(errorColor), - ], - }); - } - - const session = await mongoose.startSession(); - - session.startTransaction(); - - try { - // Withdraw amount from fromUserDB - fromUserDB.credits -= optionAmount; - - // Deposit amount to toUserDB - toUserDB.credits += optionAmount; - - await fromUserDB.save(); - - await toUserDB.save(); - - await session.commitTransaction(); - } catch (error) { - await session.abortTransaction(); - session.endSession(); - logger.error(`${error}`); - - return interaction.editReply({ - embeds: [ - embed - .setDescription( - "An error occurred while trying to gift credits. Please try again." - ) - .setColor(errorColor), - ], - }); - } finally { - // ending the session - session.endSession(); - } - - // Get DM user object - const dmUser = client.users.cache.get(optionUser.id); - - if (!dmUser) throw new Error("User not found"); - - // Send DM to user - await dmUser.send({ - embeds: [ - embed - .setDescription( - `${user.tag} has gifted you ${optionAmount} credits with reason: ${ - optionReason || "unspecified" - }` - ) - .setColor(successColor), - ], - }); - - return interaction.editReply({ - embeds: [ - embed - .setDescription( - `Successfully gifted ${optionAmount} credits to ${ - optionUser.tag - } with reason: ${optionReason || "unspecified"}` - ) - .setColor(successColor), - ], - }); - }, -}; diff --git a/src/plugins/commands/credits/modules/index.ts b/src/plugins/commands/credits/modules/index.ts deleted file mode 100644 index 20edcaf..0000000 --- a/src/plugins/commands/credits/modules/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import balance from "./balance"; -import gift from "./gift"; -import top from "./top"; -import work from "./work"; - -export default { balance, gift, top, work }; diff --git a/src/plugins/commands/credits/modules/work/index.ts b/src/plugins/commands/credits/modules/work/index.ts deleted file mode 100644 index ef0a550..0000000 --- a/src/plugins/commands/credits/modules/work/index.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Dependencies -import { CommandInteraction, MessageEmbed } from "discord.js"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import Chance from "chance"; - -// Configurations -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - -// Handlers -import logger from "../../../../../logger"; - -// Models -import * as cooldown from "../../../../../helpers/cooldown"; - -// Helpers -import fetchUser from "../../../../../helpers/fetchUser"; -import fetchGuild from "../../../../../helpers/fetchGuild"; - -export default { - metadata: { guildOnly: true, ephemeral: true }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command.setName("work").setDescription(`Work to earn credits`); - }, - execute: async (interaction: CommandInteraction) => { - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); // Destructure member - const { guild, user } = interaction; - - const embed = new MessageEmbed() - .setTitle("[:dollar:] Work") - .setTimestamp(new Date()) - .setFooter({ - text: footerText, - iconURL: footerIcon, - }); - - // Chance module - const chance = new Chance(); - - if (guild === null) { - return logger?.silly(`Guild is null`); - } - - const guildDB = await fetchGuild(guild); - - await cooldown.command(interaction, guildDB?.credits?.workTimeout); - - const creditsEarned = chance.integer({ - min: 0, - max: guildDB?.credits?.workRate, - }); - - const userDB = await fetchUser(user, guild); - - if (userDB === null) { - return logger?.silly(`User not found`); - } - - userDB.credits += creditsEarned; - - await userDB?.save()?.then(async () => { - logger?.silly( - `User ${userDB?.userId} worked and earned ${creditsEarned} credits` - ); - - return interaction.editReply({ - embeds: [ - embed - .setDescription(`You worked and earned ${creditsEarned} credits.`) - .setColor(successColor), - ], - }); - }); - }, -}; diff --git a/src/plugins/commands/dns/modules/index.ts b/src/plugins/commands/dns/modules/index.ts deleted file mode 100644 index 92a80ac..0000000 --- a/src/plugins/commands/dns/modules/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import lookup from "./lookup"; - -export default { lookup }; diff --git a/src/plugins/commands/fun/index.ts b/src/plugins/commands/fun/index.ts deleted file mode 100644 index e0337a6..0000000 --- a/src/plugins/commands/fun/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; -import logger from "../../../logger"; - -import modules from "../../commands/fun/modules"; - -export const builder = new SlashCommandBuilder() - .setName("fun") - .setDescription("Fun commands.") - - .addSubcommand(modules.meme.builder); - -export const moduleData = modules; - -export const execute = async (interaction: CommandInteraction) => { - const { options } = interaction; - - if (options.getSubcommand() === "meme") { - await modules.meme.execute(interaction); - } else { - logger.silly(`Unknown subcommand ${options.getSubcommand()}`); - } -}; diff --git a/src/plugins/commands/fun/modules/index.ts b/src/plugins/commands/fun/modules/index.ts deleted file mode 100644 index 53aeddc..0000000 --- a/src/plugins/commands/fun/modules/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import meme from "./meme"; - -export default { - meme, -}; diff --git a/src/plugins/commands/manage/index.ts b/src/plugins/commands/manage/index.ts deleted file mode 100644 index 1b61f39..0000000 --- a/src/plugins/commands/manage/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -//Dependencies -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -// Groups -import modules from "../../commands/manage/modules"; - -export const moduleData = modules; - -// Function -export const builder = new SlashCommandBuilder() - .setName("manage") - .setDescription("Manage the bot.") - .addSubcommandGroup(modules.counters.builder) - .addSubcommandGroup(modules.credits.builder); - -export const execute = async (interaction: CommandInteraction) => { - // Destructure - const { options } = interaction; - - if (options?.getSubcommandGroup() === "credits") { - return modules.credits.execute(interaction); - } - - if (options?.getSubcommandGroup() === "counters") { - return modules.counters.execute(interaction); - } -}; diff --git a/src/plugins/commands/manage/modules/counters/index.ts b/src/plugins/commands/manage/modules/counters/index.ts deleted file mode 100644 index dbafa9f..0000000 --- a/src/plugins/commands/manage/modules/counters/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Dependencies -import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -import logger from "../../../../../logger"; - -// Modules -import modules from "./modules"; - -// Function -export const moduleData = modules; - -export const builder = (group: SlashCommandSubcommandGroupBuilder) => { - return group - .setName("counters") - .setDescription("Manage guild counters.") - .addSubcommand(modules.add.builder) - .addSubcommand(modules.remove.builder); -}; - -export const execute = async (interaction: CommandInteraction) => { - const { options } = interaction; - - if (options?.getSubcommand() === "add") { - logger?.silly(`Executing create subcommand`); - - return modules.add.execute(interaction); - } - - if (options?.getSubcommand() === "remove") { - logger?.silly(`Executing delete subcommand`); - - return modules.remove.execute(interaction); - } - - logger?.silly(`Unknown subcommand ${options?.getSubcommand()}`); -}; diff --git a/src/plugins/commands/manage/modules/counters/modules/add/index.ts b/src/plugins/commands/manage/modules/counters/modules/add/index.ts deleted file mode 100644 index d00a319..0000000 --- a/src/plugins/commands/manage/modules/counters/modules/add/index.ts +++ /dev/null @@ -1,95 +0,0 @@ -// Dependencies -import { MessageEmbed, CommandInteraction, Permissions } from "discord.js"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import { ChannelType } from "discord-api-types/v10"; - -// Configurations -import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; - -import logger from "../../../../../../../logger"; - -// Models -import counterSchema from "../../../../../../../models/counter"; - -// Function -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("add") - .setDescription("Add a counter to your guild.") - .addChannelOption((option) => - option - .setName("channel") - .setDescription("The channel to send the counter to.") - .setRequired(true) - .addChannelTypes(ChannelType.GuildText) - ) - .addStringOption((option) => - option - .setName("word") - .setDescription("The word to use for the counter.") - .setRequired(true) - ) - .addNumberOption((option) => - option - .setName("start") - .setDescription("The starting value of the counter.") - ); - }, - execute: async (interaction: CommandInteraction) => { - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); - const { options, guild } = interaction; - - const discordChannel = options?.getChannel("channel"); - const countingWord = options?.getString("word"); - const startValue = options?.getNumber("start"); - - const embed = new MessageEmbed() - .setTitle("[:toolbox:] Counters - Add") - .setTimestamp(new Date()) - .setFooter({ text: footerText, iconURL: footerIcon }); - - const counter = await counterSchema?.findOne({ - guildId: guild?.id, - channelId: discordChannel?.id, - }); - - if (counter) { - return interaction?.editReply({ - embeds: [ - embed - .setDescription(`A counter already exists for this channel.`) - .setColor(errorColor), - ], - }); - } - - await counterSchema - ?.create({ - guildId: guild?.id, - channelId: discordChannel?.id, - word: countingWord, - counter: startValue || 0, - }) - .then(async () => { - logger?.silly(`Created counter`); - - return interaction?.editReply({ - embeds: [ - embed - .setDescription( - `Successfully created counter for ${discordChannel?.name}.` - ) - .setColor(successColor), - ], - }); - }); - }, -}; diff --git a/src/plugins/commands/manage/modules/counters/modules/index.ts b/src/plugins/commands/manage/modules/counters/modules/index.ts deleted file mode 100644 index bc9da9c..0000000 --- a/src/plugins/commands/manage/modules/counters/modules/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import add from "./add"; -import remove from "./remove"; - -export default { add, remove }; diff --git a/src/plugins/commands/manage/modules/counters/modules/remove/index.ts b/src/plugins/commands/manage/modules/counters/modules/remove/index.ts deleted file mode 100644 index 563829b..0000000 --- a/src/plugins/commands/manage/modules/counters/modules/remove/index.ts +++ /dev/null @@ -1,88 +0,0 @@ -// Dependencies -import { CommandInteraction, MessageEmbed, Permissions } from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; - -// Handlers -import logger from "../../../../../../../logger"; - -// Models -import counterSchema from "../../../../../../../models/counter"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import { ChannelType } from "discord-api-types/v10"; - -// Function -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("remove") - .setDescription(`Delete a counter from your guild.`) - .addChannelOption((option) => - option - .setName("channel") - .setDescription("The channel to delete the counter from.") - .setRequired(true) - .addChannelTypes(ChannelType.GuildText) - ); - }, - execute: async (interaction: CommandInteraction) => { - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); - const { options, guild } = interaction; - - const discordChannel = options?.getChannel("channel"); - - const embed = new MessageEmbed() - .setTitle("[:toolbox:] Counters - Remove") - .setTimestamp(new Date()) - .setFooter({ text: footerText, iconURL: footerIcon }); - - const counter = await counterSchema?.findOne({ - guildId: guild?.id, - channelId: discordChannel?.id, - }); - - if (counter === null) { - logger?.silly(`Counter is null`); - - return interaction?.editReply({ - embeds: [ - embed - .setDescription( - ":x: There is no counter in this channel. Please add a counter first." - ) - .setColor(errorColor), - ], - }); - } - - await counterSchema - ?.deleteOne({ - guildId: guild?.id, - channelId: discordChannel?.id, - }) - ?.then(async () => { - logger?.silly(`Counter deleted`); - - return interaction?.editReply({ - embeds: [ - embed - .setDescription( - ":white_check_mark: Counter deleted successfully." - ) - .setColor(successColor), - ], - }); - }) - .catch(async (error) => { - logger?.error(`Error deleting counter: ${error}`); - }); - }, -}; diff --git a/src/plugins/commands/manage/modules/credits/index.ts b/src/plugins/commands/manage/modules/credits/index.ts deleted file mode 100644 index 92efd12..0000000 --- a/src/plugins/commands/manage/modules/credits/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { CommandInteraction } from "discord.js"; -import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; - -import modules from "./modules"; - -export const moduleData = modules; - -export const builder = (group: SlashCommandSubcommandGroupBuilder) => { - return group - .setName("credits") - .setDescription("Manage the credits of a user.") - .addSubcommand(modules.give.builder) - .addSubcommand(modules.set.builder) - .addSubcommand(modules.take.builder) - .addSubcommand(modules.transfer.builder) - .addSubcommand(modules.giveaway.builder); -}; - -export const execute = async (interaction: CommandInteraction) => { - switch (interaction.options.getSubcommand()) { - case "give": - return modules.give.execute(interaction); - case "set": - return modules.set.execute(interaction); - case "take": - return modules.take.execute(interaction); - case "transfer": - return modules.transfer.execute(interaction); - case "giveaway": - return modules.giveaway.execute(interaction); - } -}; diff --git a/src/plugins/commands/manage/modules/credits/modules/give/index.ts b/src/plugins/commands/manage/modules/credits/modules/give/index.ts deleted file mode 100644 index 9615f73..0000000 --- a/src/plugins/commands/manage/modules/credits/modules/give/index.ts +++ /dev/null @@ -1,162 +0,0 @@ -// Dependencies -import { CommandInteraction, MessageEmbed, Permissions } from "discord.js"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -// Configurations -import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; - -// Handlers -import logger from "../../../../../../../logger"; - -// Helpers -import pluralize from "../../../../../../../helpers/pluralize"; - -// Models -import fetchUser from "../../../../../../../helpers/fetchUser"; - -// Function -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("give") - .setDescription("Give credits to a user.") - .addUserOption((option) => - option - .setName("user") - .setDescription("The user to give credits to.") - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName("amount") - .setDescription(`The amount of credits to give.`) - .setRequired(true) - ); - }, - execute: async (interaction: CommandInteraction) => { - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); // Destructure - const { guild, options } = interaction; - - const discordReceiver = options?.getUser("user"); - const creditAmount = options?.getInteger("amount"); - - // If amount option is null - if (creditAmount === null) { - logger?.silly(`Amount is null`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Give)") - .setDescription(`You must provide an amount.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - // If amount is zero or below - if (creditAmount <= 0) { - logger?.silly(`Amount is zero or below`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Give)") - .setDescription(`You must provide an amount greater than zero.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - if (discordReceiver === null) { - logger?.silly(`Discord receiver is null`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Give)") - .setDescription(`You must provide a user.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - if (guild === null) { - logger?.silly(`Guild is null`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Give)") - .setDescription(`You must be in a guild.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - const toUser = await fetchUser(discordReceiver, guild); - - if (toUser === null) { - logger?.silly(`To user is null`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Give)") - .setDescription(`The user you provided could not be found.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - if (toUser?.credits === null) { - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Give)") - .setDescription(`The user you provided does not have any credits.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - // Deposit amount to toUser - toUser.credits += creditAmount; - - // Save toUser - await toUser?.save()?.then(async () => { - logger?.silly(`Saved toUser`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Give)") - .setDescription( - `Successfully gave ${pluralize(creditAmount, "credit")}` - ) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - }); - }, -}; diff --git a/src/plugins/commands/manage/modules/credits/modules/index.ts b/src/plugins/commands/manage/modules/credits/modules/index.ts deleted file mode 100644 index 96acc29..0000000 --- a/src/plugins/commands/manage/modules/credits/modules/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import give from "./give"; -import set from "./set"; -import take from "./take"; -import transfer from "./transfer"; -import giveaway from "./giveaway"; - -export default { give, set, take, transfer, giveaway }; diff --git a/src/plugins/commands/manage/modules/credits/modules/transfer/index.ts b/src/plugins/commands/manage/modules/credits/modules/transfer/index.ts deleted file mode 100644 index dca0091..0000000 --- a/src/plugins/commands/manage/modules/credits/modules/transfer/index.ts +++ /dev/null @@ -1,256 +0,0 @@ -// Dependencies -import { CommandInteraction, MessageEmbed, Permissions } from "discord.js"; - -import mongoose from "mongoose"; - -// Configurations -import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; - -// Handlers -import logger from "../../../../../../../logger"; - -// Models -import fetchUser from "../../../../../../../helpers/fetchUser"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -// Function -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("transfer") - .setDescription("Transfer credits from one user to another.") - .addUserOption((option) => - option - .setName("from") - .setDescription("The user to transfer credits from.") - .setRequired(true) - ) - .addUserOption((option) => - option - .setName("to") - .setDescription("The user to transfer credits to.") - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName("amount") - .setDescription(`The amount of credits to transfer.`) - .setRequired(true) - ); - }, - execute: async (interaction: CommandInteraction) => { - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); // Destructure member - const { guild, options } = interaction; - - // Get options - const optionFromUser = options?.getUser("from"); - const optionToUser = options?.getUser("to"); - const optionAmount = options?.getInteger("amount"); - - // If amount is null - if (optionAmount === null) { - logger?.silly(`Amount is null`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Transfer)") - .setDescription(`You must provide an amount.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - if (guild === null) { - logger?.silly(`Guild is null`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Transfer)") - .setDescription(`You must be in a guild.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - if (optionFromUser === null) { - logger?.silly(`From user is null`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Transfer)") - .setDescription(`You must provide a user to transfer from.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - if (optionToUser === null) { - logger?.silly(`To user is null`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Transfer)") - .setDescription(`You must provide a user to transfer to.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - // Get fromUser object - const fromUser = await fetchUser(optionFromUser, guild); - - // Get toUser object - const toUser = await fetchUser(optionToUser, guild); - - // If toUser does not exist - if (fromUser === null) { - logger?.silly(`From user does not exist`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Transfer)") - .setDescription( - `The user you provided to transfer from does not exist.` - ) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - // If toUser.credits does not exist - if (!fromUser?.credits) { - logger?.silly(`From user does not have credits`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Transfer)") - .setDescription( - `The user you provided to transfer from does not have credits.` - ) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - // If toUser does not exist - if (toUser === null) { - logger?.silly(`To user does not exist`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Transfer)") - .setDescription( - `The user you provided to transfer to does not exist.` - ) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - // If toUser.credits does not exist - if (toUser?.credits === null) { - logger?.silly(`To user does not have credits`); - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Transfer)") - .setDescription( - `The user you provided to transfer to does not have credits.` - ) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - const session = await mongoose.startSession(); - - session.startTransaction(); - - try { - // Withdraw amount from fromUserDB - fromUser.credits -= optionAmount; - - // Deposit amount to toUserDB - toUser.credits += optionAmount; - - await fromUser.save(); - - await toUser.save(); - - await session.commitTransaction(); - } catch (error) { - await session.abortTransaction(); - session.endSession(); - logger.error(`${error}`); - - return interaction.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Transfer)") - .setDescription( - "An error occurred while trying to gift credits. Please try again." - ) - .setColor(errorColor) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } finally { - // ending the session - session.endSession(); - } - - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Credits (Transfer)") - .setDescription(`Transferred ${optionAmount} credits.`) - .addFields( - { - name: `${optionFromUser?.username} Balance`, - value: `${fromUser?.credits}`, - inline: true, - }, - { - name: `${optionToUser?.username} Balance`, - value: `${toUser?.credits}`, - inline: true, - } - ) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - }, -}; diff --git a/src/plugins/commands/manage/modules/index.ts b/src/plugins/commands/manage/modules/index.ts deleted file mode 100644 index c55982f..0000000 --- a/src/plugins/commands/manage/modules/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import * as counters from "./counters"; -import * as credits from "./credits"; - -export default { counters, credits }; diff --git a/src/plugins/commands/moderation/index.ts b/src/plugins/commands/moderation/index.ts deleted file mode 100644 index 73b4994..0000000 --- a/src/plugins/commands/moderation/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -import modules from "./modules"; -export const moduleData = modules; - -export const builder = new SlashCommandBuilder() - .setName("moderation") - .setDescription("Moderation.") - - .addSubcommand(modules.prune.builder); - -export const execute = async (interaction: CommandInteraction) => { - switch (interaction.options.getSubcommand()) { - case "prune": - return modules.prune.execute(interaction); - default: - throw new Error( - `Unknown subcommand: ${interaction.options.getSubcommand()}` - ); - } -}; diff --git a/src/plugins/commands/moderation/modules/index.ts b/src/plugins/commands/moderation/modules/index.ts deleted file mode 100644 index e7dd532..0000000 --- a/src/plugins/commands/moderation/modules/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import prune from "./prune"; - -export default { prune }; diff --git a/src/plugins/commands/profile/index.ts b/src/plugins/commands/profile/index.ts deleted file mode 100644 index 68cdc73..0000000 --- a/src/plugins/commands/profile/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Dependencies -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -// Modules -import modules from "../../commands/profile/modules"; - -// Handlers -import logger from "../../../logger"; - -export const moduleData = modules; - -// Function -export const builder = new SlashCommandBuilder() - .setName("profile") - .setDescription("Check a profile.") - .addSubcommand(modules.view.builder); - -export const execute = async (interaction: CommandInteraction) => { - const { options } = interaction; - - if (options?.getSubcommand() === "view") { - logger?.silly(`Executing view subcommand`); - - return modules.view.execute(interaction); - } - - logger?.silly(`No subcommand found`); -}; diff --git a/src/plugins/commands/profile/modules/index.ts b/src/plugins/commands/profile/modules/index.ts deleted file mode 100644 index dc539f8..0000000 --- a/src/plugins/commands/profile/modules/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import view from "./view"; - -export default { view }; diff --git a/src/plugins/commands/profile/modules/view/index.ts b/src/plugins/commands/profile/modules/view/index.ts deleted file mode 100644 index 4fbd5c6..0000000 --- a/src/plugins/commands/profile/modules/view/index.ts +++ /dev/null @@ -1,91 +0,0 @@ -// Dependencies -import { CommandInteraction } from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - -// Models -import fetchUser from "../../../../../helpers/fetchUser"; - -import logger from "../../../../../logger"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -// Function -export default { - metadata: { guildOnly: true, ephemeral: false }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("view") - .setDescription("View a profile.") - .addUserOption((option) => - option.setName("target").setDescription("The profile you wish to view") - ); - }, - - execute: async (interaction: CommandInteraction) => { - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); // Destructure - const { client, options, user, guild } = interaction; - - // Target information - const target = options?.getUser("target"); - - // Discord User Information - const discordUser = await client?.users?.fetch( - `${target ? target?.id : user?.id}` - ); - - if (guild === null) { - return logger?.silly(`Guild is null`); - } - - // User Information - const userObj = await fetchUser(discordUser, guild); - - // Embed object - const embed = { - author: { - name: `${discordUser?.username}#${discordUser?.discriminator}`, - icon_url: discordUser?.displayAvatarURL(), - }, - color: successColor, - fields: [ - { - name: `:dollar: Credits`, - value: `${userObj?.credits || "Not found"}`, - inline: true, - }, - { - name: `:squeeze_bottle: Level`, - value: `${userObj?.level || "Not found"}`, - inline: true, - }, - { - name: `:squeeze_bottle: Points`, - value: `${userObj?.points || "Not found"}`, - inline: true, - }, - { - name: `:loudspeaker: Reputation`, - value: `${userObj?.reputation || "Not found"}`, - inline: true, - }, - { - name: `:rainbow_flag: Language`, - value: `${userObj?.language || "Not found"}`, - inline: true, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - }, -}; diff --git a/src/plugins/commands/reputation/index.ts b/src/plugins/commands/reputation/index.ts deleted file mode 100644 index d667343..0000000 --- a/src/plugins/commands/reputation/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Dependencies -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -// Modules -import modules from "./modules"; - -// Handlers - -export const moduleData = modules; - -// Function -export const builder = new SlashCommandBuilder() - .setName("reputation") - .setDescription("Manage reputation.") - .addSubcommand(modules.give.builder); - -export const execute = async (interaction: CommandInteraction) => { - if (interaction.options.getSubcommand() === "give") { - await modules.give.execute(interaction); - } -}; diff --git a/src/plugins/commands/reputation/modules/give/index.ts b/src/plugins/commands/reputation/modules/give/index.ts deleted file mode 100644 index f9970df..0000000 --- a/src/plugins/commands/reputation/modules/give/index.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { CommandInteraction } from "discord.js"; -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; -import { timeout } from "../../../../../config/reputation"; -import logger from "../../../../../logger"; -import fetchUser from "../../../../../helpers/fetchUser"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import * as cooldown from "../../../../../helpers/cooldown"; -import noSelfReputation from "./components/noSelfReputation"; - -export default { - metadata: { guildOnly: true, ephemeral: true }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("give") - .setDescription("Give reputation to a user") - .addUserOption((option) => - option - .setName("target") - .setDescription("The user you want to repute.") - .setRequired(true) - ) - .addStringOption((option) => - option - .setName("type") - .setDescription("What type of reputation you want to repute") - .setRequired(true) - .addChoices( - { name: "Positive", value: "positive" }, - { - name: "Negative", - value: "negative", - } - ) - ); - }, - execute: async (interaction: CommandInteraction) => { - const { options, user, guild } = interaction; - - const { successColor, footerText, footerIcon } = await getEmbedConfig( - guild - ); // Destructure - - const optionTarget = options?.getUser("target"); - const optionType = options?.getString("type"); - - if (!guild) throw new Error("Guild is undefined"); - - const userObj = await fetchUser(user, guild); - if (!userObj) throw new Error("User is undefined"); - - // Pre-checks - await noSelfReputation(optionTarget, user); - - // Check if user is on cooldown otherwise create one - await cooldown.command(interaction, timeout); - - switch (optionType) { - case "positive": - userObj.reputation += 1; - break; - case "negative": - userObj.reputation += 1; - break; - default: - throw new Error("Invalid reputation type"); - } - - await userObj.save().then(async () => { - logger.silly(`User reputation has been updated`); - - await interaction.editReply({ - embeds: [ - { - title: "[:loudspeaker:] Give", - description: `You have given a ${optionType} repute to ${optionTarget}`, - timestamp: new Date(), - color: successColor, - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - }, -}; diff --git a/src/plugins/commands/reputation/modules/index.ts b/src/plugins/commands/reputation/modules/index.ts deleted file mode 100644 index e891cf0..0000000 --- a/src/plugins/commands/reputation/modules/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import give from "./give"; - -export default { give }; diff --git a/src/plugins/commands/shop/index.ts b/src/plugins/commands/shop/index.ts deleted file mode 100644 index 697c94e..0000000 --- a/src/plugins/commands/shop/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Dependencies -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -// Modules -import modules from "./modules"; - -// Handlers -import logger from "../../../logger"; - -export const moduleData = modules; - -// Function -export const builder = new SlashCommandBuilder() - .setName("shop") - .setDescription("Shop for credits and custom roles.") - .addSubcommand(modules.cpgg.builder) - .addSubcommandGroup(modules.roles.builder); - -export const execute = async (interaction: CommandInteraction) => { - const { options } = interaction; - - if (options?.getSubcommand() === "cpgg") { - logger.silly(`Executing cpgg subcommand`); - - return modules.cpgg.execute(interaction); - } - - if (options?.getSubcommandGroup() === "roles") { - logger?.silly(`Subcommand group is roles`); - - return modules.roles.execute(interaction); - } - - logger?.silly(`No subcommand found.`); -}; diff --git a/src/plugins/commands/shop/modules/cpgg/index.ts b/src/plugins/commands/shop/modules/cpgg/index.ts deleted file mode 100644 index 365753f..0000000 --- a/src/plugins/commands/shop/modules/cpgg/index.ts +++ /dev/null @@ -1,276 +0,0 @@ -import { - CommandInteraction, - MessageActionRow, - MessageButton, -} from "discord.js"; -import { v4 as uuidv4 } from "uuid"; -import axios from "axios"; - -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - -import logger from "../../../../../logger"; -import encryption from "../../../../../handlers/encryption"; - -import pluralize from "../../../../../helpers/pluralize"; - -import apiSchema from "../../../../../models/api"; -import fetchUser from "../../../../../helpers/fetchUser"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -export default { - metadata: { guildOnly: true, ephemeral: true }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("cpgg") - .setDescription("Buy cpgg power.") - .addIntegerOption((option) => - option - .setName("amount") - .setDescription("How much credits you want to withdraw.") - .setRequired(true) - ); - }, - execute: async (interaction: CommandInteraction) => { - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); - const { options, guild, user, client } = interaction; - - const optionAmount = options?.getInteger("amount"); - - if (optionAmount === null) { - logger?.silly(`Amount is null.`); - - return interaction?.editReply({ - embeds: [ - { - title: ":dollar: Credits [Gift]", - description: "We could not read your requested amount.", - color: errorColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - } - - if (guild === null) { - return logger?.silly(`Guild is null`); - } - - const userDB = await fetchUser(user, guild); - - if (userDB === null) { - return logger?.silly(`User is null`); - } - - const dmUser = client?.users?.cache?.get(user?.id); - - if ((optionAmount || userDB?.credits) < 100) { - logger?.silly(`Amount or user credits is below 100.`); - - return interaction?.editReply({ - embeds: [ - { - title: "[:shopping_cart:] CPGG", - description: `You **can't** withdraw for __CPGG__ below **100**.`, - color: errorColor, - fields: [ - { - name: "Your balance", - value: `${pluralize(userDB?.credits, "credit")}`, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - } - - if ((optionAmount || userDB?.credits) > 1000000) { - logger?.silly(`Amount or user credits is above 1.000.000.`); - - return interaction?.editReply({ - embeds: [ - { - title: "[:shopping_cart:] CPGG", - description: - "You **can't** withdraw for __CPGG__ above **1.000.000**.", - color: errorColor, - fields: [ - { - name: "Your balance", - value: `${pluralize(userDB?.credits, "credit")}`, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - } - - if (userDB?.credits < optionAmount) { - logger?.silly(`User credits is below amount.`); - - return interaction?.editReply({ - embeds: [ - { - title: "[:shopping_cart:] CPGG", - description: `You have **insufficient** credits.`, - color: errorColor, - fields: [ - { - name: "Your balance", - value: `${pluralize(userDB?.credits, "credit")}`, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - } - - const code = uuidv4(); - - const apiCredentials = await apiSchema?.findOne({ - guildId: guild?.id, - }); - - if (!apiCredentials) return; - const url = encryption.decrypt(apiCredentials?.url); - - const api = axios?.create({ - baseURL: `${url}/api/`, - headers: { - Authorization: `Bearer ${encryption.decrypt(apiCredentials.token)}`, - }, - }); - - const shopUrl = `${url}/store`; - - const buttons = new MessageActionRow().addComponents( - new MessageButton() - .setLabel("Redeem it here") - .setStyle("LINK") - .setEmoji("🏦") - .setURL(`${shopUrl}?voucher=${code}`) - ); - - await api - - ?.post("vouchers", { - uses: 1, - code, - credits: optionAmount || userDB?.credits, - memo: `${interaction?.createdTimestamp} - ${interaction?.user?.id}`, - }) - - ?.then(async () => { - logger?.silly(`Successfully created voucher.`); - - userDB.credits -= optionAmount || userDB?.credits; - - await userDB - ?.save() - - ?.then(async () => { - logger?.silly(`Successfully saved new credits.`); - - if (!interaction.guild) throw new Error("Guild is undefined"); - - await dmUser - ?.send({ - embeds: [ - { - title: "[:shopping_cart:] CPGG", - description: `This voucher comes from **${interaction.guild.name}**.`, - fields: [ - { - name: "💶 Credits", - value: `${optionAmount || userDB?.credits}`, - inline: true, - }, - ], - color: successColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - components: [buttons], - }) - .then(async (msg) => { - return interaction?.editReply({ - embeds: [ - { - title: "[:shopping_cart:] CPGG", - description: `I have sent you the code in [DM](${msg.url})!`, - color: successColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - }) - - .catch(async (error) => { - logger?.silly(`Error saving new credits. - ${error}`); - - return interaction?.editReply({ - embeds: [ - { - title: "[:shopping_cart:] CPGG", - description: "Something went wrong.", - color: errorColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - }) - - .catch(async (error) => { - logger?.silly(`Error creating voucher. - ${error}`); - - return interaction?.editReply({ - embeds: [ - { - title: "[:shopping_cart:] CPGG", - description: "Something went wrong.", - color: errorColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - }, -}; diff --git a/src/plugins/commands/shop/modules/index.ts b/src/plugins/commands/shop/modules/index.ts deleted file mode 100644 index c3df89d..0000000 --- a/src/plugins/commands/shop/modules/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import cpgg from "./cpgg"; -import * as roles from "./roles"; - -export default { cpgg, roles }; diff --git a/src/plugins/commands/shop/modules/roles/index.ts b/src/plugins/commands/shop/modules/roles/index.ts deleted file mode 100644 index f16a962..0000000 --- a/src/plugins/commands/shop/modules/roles/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Dependencies -import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -// Handlers -import logger from "../../../../../logger"; - -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - -// Modules -import modules from "./modules"; - -import guildSchema from "../../../../../models/guild"; - -export const moduleData = modules; - -// Function -export const builder = (group: SlashCommandSubcommandGroupBuilder) => { - return group - .setName("roles") - .setDescription("Shop for custom roles.") - .addSubcommand(modules.buy.builder) - .addSubcommand(modules.cancel.builder); -}; - -export const execute = async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; - const { errorColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - const { options, guild } = interaction; - - const guildDB = await guildSchema?.findOne({ - guildId: guild?.id, - }); - - if (guildDB === null) return; - - if (!guildDB.shop.roles.status) { - logger.silly(`Shop roles disabled.`); - - return interaction?.editReply({ - embeds: [ - { - title: ":dollar: Shop - Roles", - description: "This server has disabled shop roles.", - color: errorColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - } - - if (options?.getSubcommand() === "buy") { - await modules.buy.execute(interaction); - } - - if (options?.getSubcommand() === "cancel") { - await modules.cancel.execute(interaction); - } -}; diff --git a/src/plugins/commands/shop/modules/roles/modules/buy/index.ts b/src/plugins/commands/shop/modules/roles/modules/buy/index.ts deleted file mode 100644 index 23f91dc..0000000 --- a/src/plugins/commands/shop/modules/roles/modules/buy/index.ts +++ /dev/null @@ -1,143 +0,0 @@ -// Dependencies -import { - CommandInteraction, - ColorResolvable, - GuildMemberRoleManager, -} from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; - -// Models -import shopRolesSchema from "../../../../../../../models/shopRole"; -import guildSchema from "../../../../../../../models/guild"; - -import logger from "../../../../../../../logger"; - -// Helpers -import pluralize from "../../../../../../../helpers/pluralize"; -import fetchUser from "../../../../../../../helpers/fetchUser"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -// Function -export default { - metadata: { guildOnly: true, ephemeral: true }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("buy") - .setDescription("Buy a custom role.") - .addStringOption((option) => - option - .setName("name") - .setDescription("Name of the role you wish to buy.") - .setRequired(true) - ) - .addStringOption((option) => - option - .setName("color") - .setDescription("Color of the role you wish to buy.") - .setRequired(true) - ); - }, - execute: async (interaction: CommandInteraction) => { - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); - const { options, guild, user, member } = interaction; - - const optionName = options?.getString("name"); - const optionColor = options?.getString("color"); - - // If amount is null - if (optionName === null) { - logger?.silly(`Name is null.`); - - return interaction?.editReply({ - embeds: [ - { - title: ":dollar: Shop - Roles [Buy]", - description: "We could not read your requested name.", - color: errorColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - } - - await guild?.roles - .create({ - name: optionName, - color: optionColor as ColorResolvable, - reason: `${user?.id} bought from shop`, - }) - .then(async (role) => { - // Get guild object - const guildDB = await guildSchema?.findOne({ - guildId: guild?.id, - }); - - const userDB = await fetchUser(user, guild); - - if (userDB === null) { - return logger?.silly(`User is null`); - } - - if (guildDB === null) { - return logger?.silly(`Guild is null`); - } - - if (guildDB.shop === null) { - return logger?.silly(`Shop is null`); - } - - const { pricePerHour } = guildDB.shop.roles; - - userDB.credits -= pricePerHour; - - await userDB?.save(); - - await shopRolesSchema?.create({ - roleId: role?.id, - userId: user?.id, - guildId: guild?.id, - pricePerHour, - lastPayed: new Date(), - }); - - await (member?.roles as GuildMemberRoleManager)?.add(role?.id); - - logger?.silly(`Role ${role?.name} was bought by ${user?.tag}`); - - return interaction?.editReply({ - embeds: [ - { - title: ":shopping_cart: Shop - Roles [Buy]", - description: `You bought **${optionName}** for **${pluralize( - pricePerHour, - "credit" - )}**.`, - color: successColor, - fields: [ - { - name: "Your balance", - value: `${pluralize(userDB?.credits, "credit")}`, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }) - .catch(async (error) => { - return logger?.silly(`Role could not be created. ${error}`); - }); - }, -}; diff --git a/src/plugins/commands/shop/modules/roles/modules/cancel/index.ts b/src/plugins/commands/shop/modules/roles/modules/cancel/index.ts deleted file mode 100644 index 5ff483e..0000000 --- a/src/plugins/commands/shop/modules/roles/modules/cancel/index.ts +++ /dev/null @@ -1,108 +0,0 @@ -// Dependencies -import { CommandInteraction, GuildMemberRoleManager } from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; - -// Models -import shopRolesSchema from "../../../../../../../models/shopRole"; - -import logger from "../../../../../../../logger"; - -// Helpers -import pluralize from "../../../../../../../helpers/pluralize"; -import fetchUser from "../../../../../../../helpers/fetchUser"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -// Function -export default { - metadata: { guildOnly: true, ephemeral: true }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("cancel") - .setDescription("Cancel a purchase.") - .addRoleOption((option) => - option - .setName("role") - .setDescription("Role you wish to cancel.") - .setRequired(true) - ); - }, - execute: async (interaction: CommandInteraction) => { - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); - const { options, guild, user, member } = interaction; - - const optionRole = options.getRole("role"); - - if (optionRole === null) { - logger?.silly(`Role is null.`); - - return interaction?.editReply({ - embeds: [ - { - title: ":dollar: Shop - Roles [Cancel]", - description: "We could not read your requested role.", - color: errorColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - } - - const roleExist = await shopRolesSchema?.findOne({ - guildId: guild?.id, - userId: user?.id, - roleId: optionRole?.id, - }); - - if (roleExist === null) return; - - await (member?.roles as GuildMemberRoleManager)?.remove(optionRole?.id); - - await guild?.roles - .delete(optionRole?.id, `${user?.id} canceled from shop`) - .then(async () => { - const userDB = await fetchUser(user, guild); - - if (userDB === null) { - return logger?.silly(`User is null`); - } - - await shopRolesSchema?.deleteOne({ - roleId: optionRole?.id, - userId: user?.id, - guildId: guild?.id, - }); - - return interaction?.editReply({ - embeds: [ - { - title: ":shopping_cart: Shop - Roles [Cancel]", - description: `You have canceled ${optionRole.name}.`, - color: successColor, - fields: [ - { - name: "Your balance", - value: `${pluralize(userDB?.credits, "credit")}`, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }) - .catch(async (error) => { - return logger?.silly(`Role could not be deleted. ${error}`); - }); - }, -}; diff --git a/src/plugins/commands/shop/modules/roles/modules/index.ts b/src/plugins/commands/shop/modules/roles/modules/index.ts deleted file mode 100644 index b9e1626..0000000 --- a/src/plugins/commands/shop/modules/roles/modules/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import buy from "./buy"; -import cancel from "./cancel"; - -export default { - buy, - cancel, -}; diff --git a/src/plugins/commands/utility/index.ts b/src/plugins/commands/utility/index.ts deleted file mode 100644 index 43da584..0000000 --- a/src/plugins/commands/utility/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -import modules from "./modules"; -export const moduleData = modules; - -export const builder = new SlashCommandBuilder() - .setName("utility") - .setDescription("Common utility.") - - .addSubcommand(modules.about.builder) - .addSubcommand(modules.stats.builder) - .addSubcommand(modules.avatar.builder) - .addSubcommand(modules.ping.builder); - -export const execute = async (interaction: CommandInteraction) => { - switch (interaction.options.getSubcommand()) { - case "about": - return modules.about.execute(interaction); - case "stats": - return modules.stats.execute(interaction); - case "avatar": - return modules.avatar.execute(interaction); - case "ping": - return modules.ping.execute(interaction); - default: - throw new Error( - `Unknown subcommand: ${interaction.options.getSubcommand()}` - ); - } -}; diff --git a/src/plugins/commands/utility/modules/about/index.ts b/src/plugins/commands/utility/modules/about/index.ts deleted file mode 100644 index 92af9cb..0000000 --- a/src/plugins/commands/utility/modules/about/index.ts +++ /dev/null @@ -1,76 +0,0 @@ -// Dependencies -import { - CommandInteraction, - MessageActionRow, - MessageButton, -} from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; - -import { hosterName, hosterUrl } from "../../../../../config/other"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -// Function -export default { - metadata: { guildOnly: false, ephemeral: false }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command.setName("about").setDescription("About this bot!)"); - }, - execute: async (interaction: CommandInteraction) => { - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - - const buttons = new MessageActionRow().addComponents( - new MessageButton() - .setLabel("Source Code") - .setStyle("LINK") - .setEmoji("📄") - .setURL("https://github.com/ZynerOrg/xyter"), - new MessageButton() - .setLabel("Documentation") - .setStyle("LINK") - .setEmoji("📚") - .setURL("https://xyter.zyner.org"), - new MessageButton() - .setLabel("Website") - .setStyle("LINK") - .setEmoji("🌐") - .setURL("https://zyner.org"), - new MessageButton() - .setLabel("Get Help") - .setStyle("LINK") - .setEmoji("💬") - .setURL("https://discord.zyner.org"), - new MessageButton() - .setLabel(`Hosted by ${hosterName}`) - .setStyle("LINK") - .setEmoji("⚒️") - .setURL(`${hosterUrl}`) - ); - - const interactionEmbed = { - title: "[:tools:] About", - description: ` - **Xyter**'s goal is to provide a __privacy-friendly__ discord bot. - We created **Xyter** to **replace the mess** of having a dozen or so bots in __your__ community. - On top of this, you can also see our **source code** for **security** and **privacy** issues. - As well as making your own **fork** of the bot, you can also get **help** from our community. - - Developed with ❤️ by **Zyner**, a non-profit project by teens. - `, - color: successColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }; - await interaction.editReply({ - embeds: [interactionEmbed], - components: [buttons], - }); - }, -}; diff --git a/src/plugins/commands/utility/modules/index.ts b/src/plugins/commands/utility/modules/index.ts deleted file mode 100644 index 33fa0f5..0000000 --- a/src/plugins/commands/utility/modules/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import avatar from "./avatar"; -import about from "./about"; -import stats from "./stats"; -import ping from "./ping"; - -export default { - avatar, - about, - stats, - ping, -}; diff --git a/src/plugins/events/guildCreate/index.ts b/src/plugins/events/guildCreate/index.ts deleted file mode 100644 index 60d5a3c..0000000 --- a/src/plugins/events/guildCreate/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Guild } from "discord.js"; -import updatePresence from "../../../helpers/updatePresence"; -import fetchGuild from "../../../helpers/fetchGuild"; -import logger from "../../../logger"; -import { IEventOptions } from "../../../interfaces/EventOptions"; - -export const options: IEventOptions = { - type: "on", -}; - -export const execute = async (guild: Guild) => { - const { client } = guild; - - logger?.silly(`Added to guild: ${guild.name} (${guild.id})`); - - await fetchGuild(guild); - await updatePresence(client); - - logger.silly(`guildCreate: ${guild}`); -}; diff --git a/src/plugins/events/guildDelete/index.ts b/src/plugins/events/guildDelete/index.ts deleted file mode 100644 index 146351f..0000000 --- a/src/plugins/events/guildDelete/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -// 3rd party dependencies -import { Guild } from "discord.js"; - -// Dependencies -import updatePresence from "../../../helpers/updatePresence"; -import dropGuild from "../../../helpers/dropGuild"; -import logger from "../../../logger"; -import { IEventOptions } from "../../../interfaces/EventOptions"; - -export const options: IEventOptions = { - type: "on", -}; - -export const execute = async (guild: Guild) => { - const { client } = guild; - - logger?.silly(`Deleted from guild: ${guild.name} (${guild.id})`); - - await dropGuild(guild); - await updatePresence(client); - - logger.silly(`guildDelete: ${guild}`); -}; diff --git a/src/plugins/events/guildMemberAdd/index.ts b/src/plugins/events/guildMemberAdd/index.ts deleted file mode 100644 index 8310579..0000000 --- a/src/plugins/events/guildMemberAdd/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -// 3rd party dependencies -import { GuildMember } from "discord.js"; - -// Dependencies -import updatePresence from "../../../helpers/updatePresence"; -import fetchUser from "../../../helpers/fetchUser"; -import logger from "../../../logger"; - -import joinMessage from "./joinMessage"; -import audits from "./audits"; - -import { IEventOptions } from "../../../interfaces/EventOptions"; - -export const options: IEventOptions = { - type: "on", -}; - -export const execute = async (member: GuildMember) => { - const { client, user, guild } = member; - - logger.silly( - `New member: ${user.tag} (${user.id}) added to guild: ${guild.name} (${guild.id})` - ); - - await audits.execute(member); - await joinMessage.execute(member); - await fetchUser(user, guild); - await updatePresence(client); -}; diff --git a/src/plugins/events/guildMemberAdd/joinMessage.ts b/src/plugins/events/guildMemberAdd/joinMessage.ts deleted file mode 100644 index 47f9043..0000000 --- a/src/plugins/events/guildMemberAdd/joinMessage.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { GuildMember, MessageEmbed, TextChannel } from "discord.js"; - -import guildSchema from "../../../models/guild"; - -import getEmbedConfig from "../../../helpers/getEmbedConfig"; - -export default { - execute: async (member: GuildMember) => { - const { footerText, footerIcon, successColor } = await getEmbedConfig( - member.guild - ); - - const guildData = await guildSchema.findOne({ guildId: member.guild.id }); - - const { client } = member; - - if (guildData === null) return; - - if (guildData.welcome.status !== true) return; - if (!guildData.welcome.joinChannel) return; - - const channel = client.channels.cache.get( - `${guildData.welcome.joinChannel}` - ); - - if (channel === null) return; - - (channel as TextChannel).send({ - embeds: [ - new MessageEmbed() - .setColor(successColor) - .setTitle(`${member.user.username} has joined the server!`) - .setThumbnail(member.user.displayAvatarURL()) - .setDescription( - guildData.welcome.joinChannelMessage || - "Configure a join message in the `/settings guild welcome`." - ) - .setTimestamp() - .setFooter({ - text: footerText, - iconURL: footerIcon, - }), - ], - }); - }, -}; diff --git a/src/plugins/events/guildMemberRemove/leaveMessage.ts b/src/plugins/events/guildMemberRemove/leaveMessage.ts deleted file mode 100644 index daae364..0000000 --- a/src/plugins/events/guildMemberRemove/leaveMessage.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { GuildMember, MessageEmbed, TextChannel } from "discord.js"; - -import guildSchema from "../../../models/guild"; - -import getEmbedConfig from "../../../helpers/getEmbedConfig"; - -export default { - execute: async (member: GuildMember) => { - const { footerText, footerIcon, errorColor } = await getEmbedConfig( - member.guild - ); - - const guildData = await guildSchema.findOne({ guildId: member.guild.id }); - - const { client } = member; - - if (guildData === null) return; - - if (guildData.welcome.status !== true) return; - if (!guildData.welcome.leaveChannel) return; - - const channel = client.channels.cache.get( - `${guildData.welcome.leaveChannel}` - ); - - if (channel === null) return; - - (channel as TextChannel).send({ - embeds: [ - new MessageEmbed() - .setColor(errorColor) - .setTitle(`${member.user.username} has left the server!`) - .setThumbnail(member.user.displayAvatarURL()) - .setDescription( - guildData.welcome.leaveChannelMessage || - "Configure a leave message in the `/settings guild welcome`." - ) - .setTimestamp() - .setFooter({ - text: footerText, - iconURL: footerIcon, - }), - ], - }); - }, -}; diff --git a/src/plugins/events/interactionCreate/handlers/command/index.ts b/src/plugins/events/interactionCreate/handlers/command/index.ts deleted file mode 100644 index b6b30ec..0000000 --- a/src/plugins/events/interactionCreate/handlers/command/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Dependencies -import { Interaction } from "discord.js"; - -import deferReply from "../../../../../helpers/deferReply"; -import getCommandMetadata from "../../../../../helpers/getCommandMetadata"; -import * as cooldown from "../../../../../helpers/cooldown"; - -export default async (interaction: Interaction) => { - if (!interaction.isCommand()) return; - const { client, commandName } = interaction; - - const currentCommand = client.commands.get(commandName); - if (!currentCommand) throw new Error(`Unknown command ${commandName}`); - - const metadata = await getCommandMetadata(interaction, currentCommand); - await deferReply(interaction, metadata.ephemeral || false); - - if (metadata.guildOnly && !interaction.guild) - throw new Error("This command is guild only."); - - if ( - metadata.permissions && - metadata.guildOnly && - !interaction.memberPermissions?.has(metadata.permissions) - ) - throw new Error("You don't have the required permissions"); - - if (metadata.dmOnly && interaction.guild) - throw new Error("This command is only available in DM"); - - if (metadata.cooldown) await cooldown.command(interaction, metadata.cooldown); - - await currentCommand.execute(interaction); -}; diff --git a/src/plugins/events/interactionCreate/handlers/index.ts b/src/plugins/events/interactionCreate/handlers/index.ts deleted file mode 100644 index 85f1e7a..0000000 --- a/src/plugins/events/interactionCreate/handlers/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Interaction } from "discord.js"; - -import button from "./button"; -import command from "./command"; - -export const execute = async (interaction: Interaction) => { - await button(interaction); - await command(interaction); -}; diff --git a/src/plugins/events/interactionCreate/index.ts b/src/plugins/events/interactionCreate/index.ts deleted file mode 100644 index aa6e763..0000000 --- a/src/plugins/events/interactionCreate/index.ts +++ /dev/null @@ -1,48 +0,0 @@ -// 3rd party dependencies -import { CommandInteraction, MessageEmbed } from "discord.js"; - -// Dependencies -import * as handlers from "./handlers"; - -import logger from "../../../logger"; -import audits from "./audits"; -import { IEventOptions } from "../../../interfaces/EventOptions"; -import capitalizeFirstLetter from "../../../helpers/capitalizeFirstLetter"; -import getEmbedConfig from "../../../helpers/getEmbedConfig"; - -export const options: IEventOptions = { - type: "on", -}; - -export const execute = async (interaction: CommandInteraction) => { - const { guild, id } = interaction; - - logger?.silly( - `New interaction: ${id} in guild: ${guild?.name} (${guild?.id})` - ); - - const { errorColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - - await audits.execute(interaction); - - await handlers.execute(interaction).catch(async (err) => { - logger.debug(`${err}`); - - return interaction.editReply({ - embeds: [ - new MessageEmbed() - .setTitle( - `[:x:] ${capitalizeFirstLetter( - interaction.options.getSubcommand() - )}` - ) - .setDescription(`${"``"}${err}${"``"}`) - .setColor(errorColor) - .setTimestamp(new Date()) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - }); -}; diff --git a/src/plugins/events/messageCreate/modules/counters/index.ts b/src/plugins/events/messageCreate/modules/counters/index.ts deleted file mode 100644 index 1a3c7a3..0000000 --- a/src/plugins/events/messageCreate/modules/counters/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Message } from "discord.js"; - -import logger from "../../../../../logger"; -import counterSchema from "../../../../../models/counter"; - -export default { - execute: async (message: Message) => { - const { guild, author, content, channel } = message; - - if (guild == null) return; - if (author.bot) return; - if (channel?.type !== "GUILD_TEXT") return; - - const messages = await message.channel.messages.fetch({ limit: 2 }); - const lastMessage = messages.last(); - - const { id: guildId } = guild; - const { id: channelId } = channel; - - const counter = await counterSchema.findOne({ - guildId, - channelId, - }); - - if (counter === null) { - logger.silly( - `No counter found for guild ${guildId} and channel ${channelId}` - ); - return; - } - - if ( - lastMessage?.author.id === author.id && - channel.id === counter.channelId - ) { - logger.silly( - `${author.username} sent the last message therefor not allowing again.` - ); - await message.delete(); - return; - } - - if (content !== counter.word) { - logger.silly( - `Counter word ${counter.word} does not match message ${content}` - ); - - await message.delete(); - return; - } - - counter.counter += 1; - await counter - .save() - .then(async () => { - logger.silly( - `Counter for guild ${guildId} and channel ${channelId} is now ${counter.counter}` - ); - }) - .catch(async (err) => { - logger.error( - `Error saving counter for guild ${guildId} and channel ${channelId}`, - err - ); - }); - - logger.silly( - `Counter word ${counter.word} was found in message ${content} from ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})` - ); - }, -}; diff --git a/src/plugins/events/messageCreate/modules/credits/index.ts b/src/plugins/events/messageCreate/modules/credits/index.ts deleted file mode 100644 index 24a5ce2..0000000 --- a/src/plugins/events/messageCreate/modules/credits/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -import logger from "../../../../../logger"; -import { Message } from "discord.js"; - -import fetchUser from "../../../../../helpers/fetchUser"; -import fetchGuild from "../../../../../helpers/fetchGuild"; - -import * as cooldown from "../../../../../helpers/cooldown"; - -export default { - execute: async (message: Message) => { - const { guild, author, content, channel } = message; - - if (guild == null) return; - if (author.bot) return; - if (channel?.type !== "GUILD_TEXT") return; - - const { id: guildId } = guild; - const { id: userId } = author; - - const guildData = await fetchGuild(guild); - const userData = await fetchUser(author, guild); - - if (content.length < guildData.credits.minimumLength) return; - - await cooldown.message( - message, - guildData.credits.timeout, - "messageCreate-credits" - ); - - userData.credits += guildData.credits.rate; - - await userData - .save() - .then(async () => { - logger.silly( - `User ${userId} in guild ${guildId} has ${userData.credits} credits` - ); - }) - .catch(async (err) => { - logger.error( - `Error saving credits for user ${userId} in guild ${guildId} - ${err}` - ); - }); - }, -}; diff --git a/src/plugins/events/messageCreate/modules/points/index.ts b/src/plugins/events/messageCreate/modules/points/index.ts deleted file mode 100644 index ed18003..0000000 --- a/src/plugins/events/messageCreate/modules/points/index.ts +++ /dev/null @@ -1,48 +0,0 @@ -import logger from "../../../../../logger"; - -import * as cooldown from "../../../../../helpers/cooldown"; - -import fetchUser from "../../../../../helpers/fetchUser"; -import fetchGuild from "../../../../../helpers/fetchGuild"; - -import { Message } from "discord.js"; -export default { - execute: async (message: Message) => { - const { guild, author, content, channel } = message; - - if (guild == null) return; - if (author.bot) return; - if (channel?.type !== "GUILD_TEXT") return; - - const guildData = await fetchGuild(guild); - const userData = await fetchUser(author, guild); - - if (content.length < guildData.credits.minimumLength) return; - - await cooldown.message( - message, - guildData.credits.timeout, - "messageCreate-points" - ); - - userData.points += guildData.points.rate; - - await userData - .save() - .then(async () => { - logger.silly( - `Successfully saved user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})` - ); - }) - .catch(async (err) => { - logger.error( - `Error saving points for user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})`, - err - ); - }); - - logger.silly( - `User ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id}) has ${userData.points} points` - ); - }, -}; diff --git a/src/plugins/events/messageDelete/modules/counter.ts b/src/plugins/events/messageDelete/modules/counter.ts deleted file mode 100644 index 5681da8..0000000 --- a/src/plugins/events/messageDelete/modules/counter.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Dependencies -import { Message } from "discord.js"; - -// Models -import counterSchema from "../../../../models/counter"; -import logger from "../../../../logger"; - -export default async (message: Message) => { - const { guild, channel, author, content } = message; - - const counter = await counterSchema?.findOne({ - guildId: guild?.id, - channelId: channel?.id, - }); - - if (counter === null) - return logger?.silly( - `No counter found for guild: ${guild?.name} (${guild?.id})` - ); - const { word } = counter; - - const messages = await message.channel.messages.fetch({ limit: 1 }); - const lastMessage = messages.last(); - - if (!lastMessage) return; - - if (content !== word) return; - - if (lastMessage.author.id === message.author.id) return; - - channel?.send(`${author} said **${word}**.`); - logger?.silly(`${author} said ${word} in ${channel}`); - return logger?.silly( - `User: ${author?.tag} (${author?.id}) in guild: ${guild?.name} (${guild?.id}) said the counter word: ${word}` - ); -}; diff --git a/src/plugins/events/messageUpdate/modules/counter.ts b/src/plugins/events/messageUpdate/modules/counter.ts deleted file mode 100644 index 4a01749..0000000 --- a/src/plugins/events/messageUpdate/modules/counter.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Dependencies -import { Message } from "discord.js"; - -// Models -import counterSchema from "../../../../models/counter"; -import logger from "../../../../logger"; - -export default async (message: Message) => { - const { guild, channel, author, content } = message; - - const counter = await counterSchema?.findOne({ - guildId: guild?.id, - channelId: channel?.id, - }); - - if (counter === null) - return logger?.silly( - `No counter found for guild: ${guild?.name} (${guild?.id})` - ); - const { word } = counter; - if (content === word) - return logger?.silly( - `User: ${author?.tag} (${author?.id}) in guild: ${guild?.name} (${guild?.id}) said the counter word: ${word}` - ); - - await message - ?.delete() - ?.then(async () => { - await channel?.send(`${author} said **${word}**.`); - logger?.silly(`${author} said ${word} in ${channel}`); - }) - ?.catch(async (error) => { - logger?.error(error); - }); -}; diff --git a/src/plugins/events/ready/index.ts b/src/plugins/events/ready/index.ts deleted file mode 100644 index 125bdc8..0000000 --- a/src/plugins/events/ready/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Dependencies -import { Client } from "discord.js"; -import logger from "../../../logger"; - -// Helpers -import updatePresence from "../../../helpers/updatePresence"; -import deployCommands from "../../../handlers/deployCommands"; -import devMode from "../../../handlers/devMode"; -import { IEventOptions } from "../../../interfaces/EventOptions"; - -export const options: IEventOptions = { - type: "once", -}; - -export const execute = async (client: Client) => { - logger.info("Discord's API client is ready!"); - - await updatePresence(client); - await devMode(client); - await deployCommands(client); -}; diff --git a/src/jobs/shop/index.ts b/src/schedules/shop/index.ts similarity index 62% rename from src/jobs/shop/index.ts rename to src/schedules/shop/index.ts index b2ef366..c99a542 100644 --- a/src/jobs/shop/index.ts +++ b/src/schedules/shop/index.ts @@ -1,12 +1,13 @@ // Dependencies import { Client } from "discord.js"; -import * as roles from "./modules/roles"; +import { execute as RolesExecute } from "./modules/roles"; export const options = { schedule: "*/5 * * * *", // https://crontab.guru/ }; +// Execute the function export const execute = async (client: Client) => { - await roles.execute(client); + await RolesExecute(client); }; diff --git a/src/schedules/shop/modules/roles/components/dueForPayment.ts b/src/schedules/shop/modules/roles/components/dueForPayment.ts new file mode 100644 index 0000000..fd08ae7 --- /dev/null +++ b/src/schedules/shop/modules/roles/components/dueForPayment.ts @@ -0,0 +1,11 @@ +import { Client } from "discord.js"; +import logger from "../../../../../middlewares/logger"; + +import { GuildShopRoles } from "@prisma/client"; + +// Execute the dueForPayment function +export const execute = (_client: Client, role: GuildShopRoles) => { + const { roleId } = role; + + logger.silly(`Shop role ${roleId} is not due for payment.`); +}; diff --git a/src/schedules/shop/modules/roles/components/overDueForPayment.ts b/src/schedules/shop/modules/roles/components/overDueForPayment.ts new file mode 100644 index 0000000..7020db7 --- /dev/null +++ b/src/schedules/shop/modules/roles/components/overDueForPayment.ts @@ -0,0 +1,128 @@ +import { Client } from "discord.js"; +import logger from "../../../../../middlewares/logger"; + +import { GuildShopRoles } from "@prisma/client"; +import prisma from "../../../../../handlers/database"; + +// Execute the component +export const execute = async (client: Client, role: GuildShopRoles) => { + const { guildId, userId, roleId } = role; + if (!userId) throw new Error("User ID not found for shop role."); + + const rGuild = client.guilds.cache.get(guildId); + if (!rGuild) throw new Error("Guild not found."); + + const rMember = await rGuild.members.fetch(userId); + if (!rMember) throw new Error("Member not found."); + + const rRole = rMember.roles.cache.get(roleId); + if (!rRole) throw new Error("Role not found."); + + logger.debug(`Shop role ${roleId} is due for payment.`); + + const getGuildMember = await prisma.guildMember.findUnique({ + where: { + userId_guildId: { + userId, + guildId, + }, + }, + include: { + user: true, + guild: true, + }, + }); + + logger.silly(getGuildMember); + + if (!getGuildMember) throw new Error("Could not find guild member."); + + const pricePerHour = getGuildMember.guild.shopRolesPricePerHour; + + if (getGuildMember.creditsEarned < pricePerHour) { + await rMember.roles + .remove(roleId) + .then(async () => { + const deleteShopRole = await prisma.guildShopRoles.delete({ + where: { + guildId_userId_roleId: { + guildId, + userId, + roleId, + }, + }, + }); + + logger.silly(deleteShopRole); + + logger.silly( + `Shop role document ${roleId} has been deleted from user ${userId}.` + ); + }) + .catch(() => { + throw new Error(`Failed removing role from user.`); + }); + + throw new Error("User does not have enough credits."); + } + + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId, + guildId, + }, + }, + update: { creditsEarned: { decrement: pricePerHour } }, + create: { + creditsEarned: -pricePerHour, + user: { + connectOrCreate: { + create: { + id: userId, + }, + where: { + id: userId, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guildId, + }, + where: { + id: guildId, + }, + }, + }, + }, + include: { + user: true, + guild: true, + }, + }); + + logger.silly(createGuildMember); + + logger.silly(`User ${userId} has been updated.`); + + const updateGuildShopRole = await prisma.guildShopRoles.update({ + where: { + guildId_userId_roleId: { + guildId, + userId, + roleId, + }, + }, + data: { + lastPayed: new Date(), + }, + }); + + logger.silly(updateGuildShopRole); + + logger.silly(`Shop role ${roleId} has been updated.`); + + logger.debug(`Shop role ${roleId} has been paid.`); +}; diff --git a/src/schedules/shop/modules/roles/index.ts b/src/schedules/shop/modules/roles/index.ts new file mode 100644 index 0000000..197cecd --- /dev/null +++ b/src/schedules/shop/modules/roles/index.ts @@ -0,0 +1,28 @@ +/* eslint-disable no-loops/no-loops */ +import { Client } from "discord.js"; +import prisma from "../../../../handlers/database"; + +import { execute as dueForPaymentExecute } from "./components/dueForPayment"; +import { execute as overDueForPaymentExecute } from "./components/overDueForPayment"; + +// Execute the roles function +export const execute = async (client: Client) => { + const roles = await prisma.guildShopRoles.findMany(); + + for await (const role of roles) { + const { lastPayed } = role; + const nextPayment = new Date(lastPayed.setHours(lastPayed.getHours() + 1)); + + const now = new Date(); + + if (nextPayment > now) { + dueForPaymentExecute(client, role); + + return; + } + + if (nextPayment < now) { + await overDueForPaymentExecute(client, role); + } + } +}; diff --git a/src/schedules/timeouts/index.ts b/src/schedules/timeouts/index.ts new file mode 100644 index 0000000..9964892 --- /dev/null +++ b/src/schedules/timeouts/index.ts @@ -0,0 +1,40 @@ +/* eslint-disable no-loops/no-loops */ +import logger from "../../middlewares/logger"; + +import addSeconds from "../../helpers/addSeconds"; + +import prisma from "../../handlers/database"; + +export const options = { + schedule: "*/30 * * * *", // https://crontab.guru/ +}; + +// Execute the job +export const execute = async () => { + const getCooldown = await prisma.cooldown.findMany(); + + for await (const timeout of getCooldown) { + const { guildId, userId, timeoutId, cooldown, createdAt } = timeout; + + const overDue = addSeconds(cooldown, createdAt) < new Date(); + + if (overDue) { + logger.info(timeout); + const deleteCooldown = await prisma.cooldown.delete({ + where: { + guildId_userId_timeoutId: { + guildId, + userId, + timeoutId, + }, + }, + }); + + logger.silly(deleteCooldown); + + logger.debug( + `Timeout document ${timeoutId} has been deleted from user ${userId}.` + ); + } + } +}; diff --git a/src/types/common/environment.d.ts b/src/types/common/environment.d.ts new file mode 100644 index 0000000..c4f32e4 --- /dev/null +++ b/src/types/common/environment.d.ts @@ -0,0 +1,26 @@ +import { ColorResolvable, Snowflake } from "discord.js"; + +declare global { + namespace NodeJS { + interface ProcessEnv { + MONGO_URL: string; + DISCORD_TOKEN: string; + DISCORD_CLIENT_ID: Snowflake; + DISCORD_GUILD_ID: Snowflake; + DEVELOPMENT_MODE: string; + ENCRYPTION_ALGORITHM: string; + ENCRYPTION_SECRET: string; + EMBED_COLOR_SUCCESS: ColorResolvable; + EMBED_COLOR_WAIT: ColorResolvable; + EMBED_COLOR_ERROR: ColorResolvable; + EMBED_FOOTER_TEXT: string; + EMBED_FOOTER_ICON: string; + LOG_LEVEL: string; + REPUTATION_TIMEOUT: string; + BOT_HOSTER_NAME: string; + BOT_HOSTER_URL: string; + } + } +} + +export {};