From 1f98e744c16855331e1eafba3c7377b74f6dc024 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 12:23:03 -0400 Subject: [PATCH 01/52] Remove source code to src folder --- {commands => src/commands}/add.js | 0 {commands => src/commands}/claim.js | 0 {commands => src/commands}/close.js | 0 {commands => src/commands}/remove.js | 0 {commands => src/commands}/rename.js | 0 {config => src/config}/config.example.jsonc | 0 {config => src/config}/token.example.json | 0 {events => src/events}/interactionCreate.js | 0 {events => src/events}/ready.js | 0 index.js => src/index.js | 0 {locales => src/locales}/cs.json | 0 {locales => src/locales}/de.json | 0 {locales => src/locales}/es.json | 0 {locales => src/locales}/fr.json | 0 {locales => src/locales}/main.json | 0 {locales => src/locales}/tr.json | 0 {utils => src/utils}/claim.js | 0 {utils => src/utils}/close.js | 0 {utils => src/utils}/close_askReason.js | 0 {utils => src/utils}/createTicket.js | 0 {utils => src/utils}/delete.js | 0 {utils => src/utils}/logs.js | 0 {utils => src/utils}/pgsqlDriver.js | 0 23 files changed, 0 insertions(+), 0 deletions(-) rename {commands => src/commands}/add.js (100%) rename {commands => src/commands}/claim.js (100%) rename {commands => src/commands}/close.js (100%) rename {commands => src/commands}/remove.js (100%) rename {commands => src/commands}/rename.js (100%) rename {config => src/config}/config.example.jsonc (100%) rename {config => src/config}/token.example.json (100%) rename {events => src/events}/interactionCreate.js (100%) rename {events => src/events}/ready.js (100%) rename index.js => src/index.js (100%) rename {locales => src/locales}/cs.json (100%) rename {locales => src/locales}/de.json (100%) rename {locales => src/locales}/es.json (100%) rename {locales => src/locales}/fr.json (100%) rename {locales => src/locales}/main.json (100%) rename {locales => src/locales}/tr.json (100%) rename {utils => src/utils}/claim.js (100%) rename {utils => src/utils}/close.js (100%) rename {utils => src/utils}/close_askReason.js (100%) rename {utils => src/utils}/createTicket.js (100%) rename {utils => src/utils}/delete.js (100%) rename {utils => src/utils}/logs.js (100%) rename {utils => src/utils}/pgsqlDriver.js (100%) diff --git a/commands/add.js b/src/commands/add.js similarity index 100% rename from commands/add.js rename to src/commands/add.js diff --git a/commands/claim.js b/src/commands/claim.js similarity index 100% rename from commands/claim.js rename to src/commands/claim.js diff --git a/commands/close.js b/src/commands/close.js similarity index 100% rename from commands/close.js rename to src/commands/close.js diff --git a/commands/remove.js b/src/commands/remove.js similarity index 100% rename from commands/remove.js rename to src/commands/remove.js diff --git a/commands/rename.js b/src/commands/rename.js similarity index 100% rename from commands/rename.js rename to src/commands/rename.js diff --git a/config/config.example.jsonc b/src/config/config.example.jsonc similarity index 100% rename from config/config.example.jsonc rename to src/config/config.example.jsonc diff --git a/config/token.example.json b/src/config/token.example.json similarity index 100% rename from config/token.example.json rename to src/config/token.example.json diff --git a/events/interactionCreate.js b/src/events/interactionCreate.js similarity index 100% rename from events/interactionCreate.js rename to src/events/interactionCreate.js diff --git a/events/ready.js b/src/events/ready.js similarity index 100% rename from events/ready.js rename to src/events/ready.js diff --git a/index.js b/src/index.js similarity index 100% rename from index.js rename to src/index.js diff --git a/locales/cs.json b/src/locales/cs.json similarity index 100% rename from locales/cs.json rename to src/locales/cs.json diff --git a/locales/de.json b/src/locales/de.json similarity index 100% rename from locales/de.json rename to src/locales/de.json diff --git a/locales/es.json b/src/locales/es.json similarity index 100% rename from locales/es.json rename to src/locales/es.json diff --git a/locales/fr.json b/src/locales/fr.json similarity index 100% rename from locales/fr.json rename to src/locales/fr.json diff --git a/locales/main.json b/src/locales/main.json similarity index 100% rename from locales/main.json rename to src/locales/main.json diff --git a/locales/tr.json b/src/locales/tr.json similarity index 100% rename from locales/tr.json rename to src/locales/tr.json diff --git a/utils/claim.js b/src/utils/claim.js similarity index 100% rename from utils/claim.js rename to src/utils/claim.js diff --git a/utils/close.js b/src/utils/close.js similarity index 100% rename from utils/close.js rename to src/utils/close.js diff --git a/utils/close_askReason.js b/src/utils/close_askReason.js similarity index 100% rename from utils/close_askReason.js rename to src/utils/close_askReason.js diff --git a/utils/createTicket.js b/src/utils/createTicket.js similarity index 100% rename from utils/createTicket.js rename to src/utils/createTicket.js diff --git a/utils/delete.js b/src/utils/delete.js similarity index 100% rename from utils/delete.js rename to src/utils/delete.js diff --git a/utils/logs.js b/src/utils/logs.js similarity index 100% rename from utils/logs.js rename to src/utils/logs.js diff --git a/utils/pgsqlDriver.js b/src/utils/pgsqlDriver.js similarity index 100% rename from utils/pgsqlDriver.js rename to src/utils/pgsqlDriver.js From fc430eb1c575dd2599195deaf9e55190d7b34ba0 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 12:24:25 -0400 Subject: [PATCH 02/52] Added tsconfig --- tsconfig.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tsconfig.json diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..07661af5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "allowJs": true, + "module": "CommonJS", + "target": "ES2016", + "strict": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "esModuleInterop": true, + "skipLibCheck": false, + "sourceMap": true, + "inlineSources": true, + "sourceRoot": "/" + } +} \ No newline at end of file From 8cb500abf255d68d0ac8590c33ceef5805ebc65e Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 12:26:13 -0400 Subject: [PATCH 03/52] Moved deploy command --- deploy-commands.js => src/deploy-commands.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename deploy-commands.js => src/deploy-commands.js (100%) diff --git a/deploy-commands.js b/src/deploy-commands.js similarity index 100% rename from deploy-commands.js rename to src/deploy-commands.js From a984f4360e86793f9ed8799e19931ae09b7667f4 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 12:34:54 -0400 Subject: [PATCH 04/52] More config for ts support --- .eslintignore | 1 + .gitignore | 3 ++- package.json | 11 ++++++++--- tsconfig.json | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.eslintignore b/.eslintignore index 5f0d576a..5235e38f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -8,3 +8,4 @@ README.md LICENSE package.json package-lock.json +dist/ diff --git a/.gitignore b/.gitignore index eb6d16ab..bccfaf5a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules json.sqlite package-lock.json config.jsonc -token.json \ No newline at end of file +token.json +dist \ No newline at end of file diff --git a/package.json b/package.json index 2ca2f25e..4d40f952 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,11 @@ { "name": "ticket-bot", - "version": "2.4.0", + "version": "2.5.0", "description": "Bot with a ticket system using Discord.js v14", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "start": "node dist/index.js", + "build": "tsc", "format:fix": "prettier --write .", "format:check": "prettier --check .", "lint:fix": "eslint --fix .", @@ -31,6 +32,7 @@ "discord.js": "^14.11.0", "fs-extra": "^11.1.1", "jsonc": "^2.0.0", + "mongoose": "^7.3.0", "mysql2": "^3.3.5", "pg": "^8.11.0", "quick.db": "^9.1.6", @@ -39,12 +41,15 @@ "websocket": "^1.0.34" }, "devDependencies": { + "@types/better-sqlite3": "^7.6.4", + "@types/node": "^20.3.1", "eslint": "^8.43.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^4.2.1", - "prettier": "^2.8.8" + "prettier": "^2.8.8", + "typescript": "^5.1.3" } } diff --git a/tsconfig.json b/tsconfig.json index 07661af5..708089da 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "rootDir": "./src", "allowJs": true, "module": "CommonJS", - "target": "ES2016", + "target": "ESNext", "strict": true, "moduleResolution": "node", "resolveJsonModule": true, From 79dacb84a490f70a3fd6d0e61e6b6db8b2bc3155 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 14:17:45 -0400 Subject: [PATCH 05/52] Added types for index.ts --- .env.example | 4 + .gitignore | 3 +- {src/config => config}/config.example.jsonc | 0 {src/config => config}/token.example.json | 0 package.json | 1 + src/Types.ts | 144 ++++++++++++++++++++ src/{index.js => index.ts} | 47 ++++--- tsconfig.json | 6 +- 8 files changed, 180 insertions(+), 25 deletions(-) create mode 100644 .env.example rename {src/config => config}/config.example.jsonc (100%) rename {src/config => config}/token.example.json (100%) create mode 100644 src/Types.ts rename src/{index.js => index.ts} (86%) diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..02cdd961 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +# Your discord token +token="" +# Prisma Database URL (refer to docs for more details) +database_url="postgresql://user:password@localhost:5432/database" \ No newline at end of file diff --git a/.gitignore b/.gitignore index bccfaf5a..054fc02f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ json.sqlite package-lock.json config.jsonc token.json -dist \ No newline at end of file +dist +.env \ No newline at end of file diff --git a/src/config/config.example.jsonc b/config/config.example.jsonc similarity index 100% rename from src/config/config.example.jsonc rename to config/config.example.jsonc diff --git a/src/config/token.example.json b/config/token.example.json similarity index 100% rename from src/config/token.example.json rename to config/token.example.json diff --git a/package.json b/package.json index 4d40f952..56a76882 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ }, "devDependencies": { "@types/better-sqlite3": "^7.6.4", + "@types/fs-extra": "^11.0.1", "@types/node": "^20.3.1", "eslint": "^8.43.0", "eslint-config-airbnb-base": "^15.0.0", diff --git a/src/Types.ts b/src/Types.ts new file mode 100644 index 00000000..0c4d03e9 --- /dev/null +++ b/src/Types.ts @@ -0,0 +1,144 @@ +import { Client, Collection, Interaction, SlashCommandBuilder } from "discord.js"; +import { QuickDB } from "quick.db"; + +// Config types and setups +type TicketQuestion = { + label: string; + placeholder: string; + style: string; + maxLength: number; +} +type TicketType = { + codeName: string; + name: string; + description: string; + categoryId: string; + ticketNameOption: string; + customDescription: string; + cantAccess: string[]; + askQuestion: boolean; + questions: TicketQuestion[]; +} +type dbConn = { + enabled: boolean; + host: string; + user: string; + password: string; + database: string; + table: string; +} +export type config = { + clientId: string; + guildId: string; + maincolor: string; + lang: string; // Tho can be cs/de/es/fr/main/tr type but we can't guarantee what users put + // Database credentials are deprecated, will be removed when Prisma are in-place. + postgre?: dbConn; + mysql?: dbConn; + closeTicketCategoryId: string; + openTicketChannelId: string; + TicketTypes: TicketType[]; + ticketNameOption: string; + ticketNamePrefixWhenClaimed: string; + rolesWhoHaveAccessToTheTickets: string[]; + rolesWhoCanNotCreateTickets: string[]; + pingRoleWhenOpened: boolean; + roleToPingWhenOpenedId: string[]; + logs: boolean; + logsChannelId: string; + claimButton: boolean; + whoCanCloseTicket: "STAFFONLY" | "EVERYONE"; + closeButton: boolean; + askReasonWhenClosing: boolean; + createTranscript: boolean; + status: { + enabled: boolean; + text: string; + type: "PLAYING" | "WATCHING" | "LISTENING" | "STREAMING" | "COMPETING", + url?: string, + status: "online" + }, + maxTicketOpened: number; +} +export type locale = { + embeds: { + openTicket: { + title: string, + description: string, + footer: { + text: string + } + }, + ticketOpened: { + title: string, + description: string, + footer: { + text: string + } + }, + ticketClosed: { + title: string, + description: string + }, + ticketClosedDM: { + title: string, + description: string, + footer: { + text: string + } + } + }, + modals: { + reasonTicketOpen: { + title: string, + label: string, + placeholder: string + }, + reasonTicketClose: { + title: string, + label: string, + placeholder: string + } + }, + buttons: { + close: { + label: string, + emoji: string + }, + claim: { + label: string, + emoji: string + } + }, + ticketOpenedMessage: string, + ticketOnlyClaimableByStaff: string, + ticketAlreadyClaimed: string, + ticketClaimedMessage: string, + ticketOnlyClosableByStaff: string, + ticketAlreadyClosed: string, + ticketCreatingTranscript: string, + ticketTranscriptCreated: string, + ticketLimitReached: string, + + other: { + openTicketButtonMSG: string, + deleteTicketButtonMSG: string, + selectTicketTypePlaceholder: string, + claimedBy: string, + noReasonGiven: string, + unavailable: string + } +} + +export type command = { + data: SlashCommandBuilder; + execute: (interaction: Interaction, client: DiscordClient) => Promise | void; +} + +export interface DiscordClient extends Client { + config: config; + db: QuickDB; + locales: locale; + msToHm: (ms: number) => string; + commands: Collection; +} diff --git a/src/index.js b/src/index.ts similarity index 86% rename from src/index.js rename to src/index.ts index e8c2be2b..47604132 100644 --- a/src/index.js +++ b/src/index.ts @@ -14,15 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -const fs = require("fs-extra"); -const path = require("node:path"); -const { Client, Collection, GatewayIntentBits } = require("discord.js"); +import { Interaction } from "discord.js"; +import fs from 'fs-extra'; +import path from 'node:path'; +import { Client, Collection, GatewayIntentBits } from 'discord.js' // eslint-disable-next-line node/no-missing-require, node/no-unpublished-require -const { token } = require("./config/token.json"); -const { QuickDB, MySQLDriver } = require("quick.db"); -const jsonc = require("jsonc"); +import { token } from '../config/token.json' +import {QuickDB, MySQLDriver } from 'quick.db' +import { jsonc } from 'jsonc'; +import { DiscordClient, config, locale } from "./Types"; -process.on("unhandledRejection", (reason, promise, a) => { +// Although invalid type, it should be good enough for now until more stuff needs to be handled here +process.on("unhandledRejection", (reason: string, promise: string, a: string) => { console.log(reason, promise, a); }); @@ -43,10 +46,11 @@ fetch("https://api.github.com/repos/Sayrix/Ticket-Bot/tags").then((res) => { if (Math.floor(res.status / 100) !== 2) return console.warn("[Version Check] Failed to pull latest version from server"); res.json().then((json) => { // Assumign the format stays consistent (i.e. x.x.x) - const latest = json[0].name.split(".").map((k) => parseInt(k)); - const current = require("./package.json") + const latest = json[0].name.split(".").map((k: string) => parseInt(k)); + //@ts-ignore allow access to package.json for version check + const current = require("../package.json") .version.split(".") - .map((k) => parseInt(k)); + .map((k: string) => parseInt(k)); if ( latest[0] > current[0] || (latest[0] === current[0] && latest[1] > current[1]) || @@ -57,20 +61,21 @@ fetch("https://api.github.com/repos/Sayrix/Ticket-Bot/tags").then((res) => { }); }); -const config = jsonc.parse(fs.readFileSync(path.join(__dirname, "config/config.jsonc"), "utf8")); + + +const config: config = jsonc.parse(fs.readFileSync(path.join(__dirname, "config/config.jsonc"), "utf8")); const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMembers], presence: { status: config.status?.status ?? "online" } -}); +}) as DiscordClient; // All variables stored in the client object -client.discord = require("discord.js"); -client.config = jsonc.parse(fs.readFileSync(path.join(__dirname, "config/config.jsonc"), "utf8")); +client.config = config -let db = null; +let db: QuickDB | undefined; if (client.config.postgre?.enabled) { // PostgreSQL Support. @@ -80,7 +85,7 @@ if (client.config.postgre?.enabled) { require.resolve("pg"); } catch (e) { console.error("pg driver is not installed!\n\nPlease run \"npm i pg\" in the console!"); - throw e.code; + throw e; } const PostgresDriver = require("./utils/pgsqlDriver"); const pgsql = new PostgresDriver({ @@ -106,7 +111,7 @@ if (client.config.postgre?.enabled) { require.resolve("mysql2"); } catch (e) { console.error("mysql2 is not installed!\n\nPlease run \"npm i mysql2\" in the console!"); - throw e.code; + throw e; } const mysql = new MySQLDriver({ @@ -131,10 +136,8 @@ if (client.config.postgre?.enabled) { client.db = db; } -client.locales = require(`./locales/${client.config.lang}.json`); -client.embeds = client.locales.embeds; -client.log = require("./utils/logs.js").log; -client.msToHm = function dhm(ms) { +client.locales = require(`./locales/${client.config.lang}.json`) as locale; +client.msToHm = function dhm(ms: number) { const days = Math.floor(ms / (24 * 60 * 60 * 1000)); const daysms = ms % (24 * 60 * 60 * 1000); const hours = Math.floor(daysms / (60 * 60 * 1000)); @@ -162,7 +165,7 @@ for (const file of commandFiles) { } // Execute commands -client.on("interactionCreate", async (interaction) => { +client.on("interactionCreate", async (interaction: Interaction) => { if (!interaction.isChatInputCommand()) return; const command = client.commands.get(interaction.commandName); if (!command) return; diff --git a/tsconfig.json b/tsconfig.json index 708089da..460279a2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,6 @@ { "compilerOptions": { "outDir": "./dist", - "rootDir": "./src", "allowJs": true, "module": "CommonJS", "target": "ESNext", @@ -13,5 +12,8 @@ "sourceMap": true, "inlineSources": true, "sourceRoot": "/" - } + }, + "include": [ + "./src/**/*", + ] } \ No newline at end of file From c090970a79d3eef64153f22a5c63501257d9da9c Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 14:22:17 -0400 Subject: [PATCH 06/52] Added eslint typescript support --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 56a76882..6ff74061 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "ticket-bot", "version": "2.5.0", "description": "Bot with a ticket system using Discord.js v14", - "main": "index.js", + "main": "dist/index.js", + "type":"module", "scripts": { "start": "node dist/index.js", "build": "tsc", @@ -44,6 +45,8 @@ "@types/better-sqlite3": "^7.6.4", "@types/fs-extra": "^11.0.1", "@types/node": "^20.3.1", + "@typescript-eslint/eslint-plugin": "^5.60.0", + "@typescript-eslint/parser": "^5.60.0", "eslint": "^8.43.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^8.8.0", From 524e9caa4880e9bc16f9db5c3d2e88f4e5c641bd Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 14:31:19 -0400 Subject: [PATCH 07/52] Converted deploy commands to ts --- .eslintrc.js | 6 ++++-- src/{deploy-commands.js => deploy-commands.ts} | 15 +++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) rename src/{deploy-commands.js => deploy-commands.ts} (76%) diff --git a/.eslintrc.js b/.eslintrc.js index baa8de66..2df9655b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,7 +5,9 @@ module.exports = { es2021: true, }, overrides: [], - extends: ["eslint:recommended", "plugin:node/recommended", "prettier"], + extends: ["eslint:recommended", "plugin:node/recommended", "prettier", "plugin:@typescript-eslint/recommended"], + parser: "@typescript-eslint/parser", + plugins: ["@typescript-eslint"], parserOptions: { ecmaVersion: "latest", }, @@ -18,6 +20,6 @@ module.exports = { "semi": ["error", "always"], "quotes": [2, "double"], "semi-style": ["error", "last"], - "no-process-exit": "off", + "no-process-exit": "off" }, }; diff --git a/src/deploy-commands.js b/src/deploy-commands.ts similarity index 76% rename from src/deploy-commands.js rename to src/deploy-commands.ts index ffbe34c0..875729f2 100644 --- a/src/deploy-commands.js +++ b/src/deploy-commands.ts @@ -1,15 +1,14 @@ -const fs = require("node:fs"); -const path = require("node:path"); -const jsonc = require("jsonc"); +import fs from "node:fs"; +import path from "node:path"; +import { jsonc } from "jsonc"; // eslint-disable-next-line node/no-extraneous-require -const { REST } = require("@discordjs/rest"); -const { Routes } = require("discord.js"); +import { REST } from "@discordjs/rest"; +import { Routes } from "discord.js"; // eslint-disable-next-line no-unused-vars -const Discord = require("discord.js"); // eslint-disable-next-line node/no-missing-require, node/no-unpublished-require -const { token } = require("./config/token.json"); +import { token } from "../config/token.json"; -module.exports = { +export default { /** * @param {Discord.Client} client */ From fb272267dae7a9cef2e881f6dbbcefda1f77f1cf Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 16:19:35 -0400 Subject: [PATCH 08/52] Replaced pgsqlDriver with types --- package.json | 3 +- src/utils/pgsqlDriver.js | 89 ------------------------------- src/utils/pgsqlDriver.ts | 111 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 90 deletions(-) delete mode 100644 src/utils/pgsqlDriver.js create mode 100644 src/utils/pgsqlDriver.ts diff --git a/package.json b/package.json index 6ff74061..9fa44c04 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "2.5.0", "description": "Bot with a ticket system using Discord.js v14", "main": "dist/index.js", - "type":"module", + "type": "module", "scripts": { "start": "node dist/index.js", "build": "tsc", @@ -45,6 +45,7 @@ "@types/better-sqlite3": "^7.6.4", "@types/fs-extra": "^11.0.1", "@types/node": "^20.3.1", + "@types/pg": "^8.10.2", "@typescript-eslint/eslint-plugin": "^5.60.0", "@typescript-eslint/parser": "^5.60.0", "eslint": "^8.43.0", diff --git a/src/utils/pgsqlDriver.js b/src/utils/pgsqlDriver.js deleted file mode 100644 index 8a776e8f..00000000 --- a/src/utils/pgsqlDriver.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Official pre-released quick.db Postgresql driver. Remove this file when the driver is officially released ~ zhiyan114 - * Why add this? PostgreSQL is a better database than MySQL. Alternative would be to use Prisma, - * but I don't want to esentially re-write the database logic and cause confusions. - * Source: https://github.com/plexidev/quick.db/blob/dev/src/drivers/PostgresDriver.ts - * LICENSE: https://github.com/plexidev/quick.db/blob/dev/LICENSE.md - */ - -// eslint-disable-next-line node/no-missing-require -const { Client } = require("pg"); - -module.exports = class PostgresDriver { - instance; - config; - conn; - - constructor(config) { - this.config = config; - } - - static createSingleton(config) { - if (!this.instance) this.instance = new PostgresDriver(config); - return this.instance; - } - - async connect() { - this.conn = new Client(this.config); - await this.conn.connect(); - } - - async disconnect() { - this.checkConnection(); - await this.conn.end(); - } - - checkConnection() { - if (!this.conn) { - throw new Error("No connection to postgres database"); - } - } - - async prepare(table) { - this.checkConnection(); - await this.conn.query(`CREATE TABLE IF NOT EXISTS ${table} (id VARCHAR(255), value TEXT)`); - } - - async getAllRows(table) { - this.checkConnection(); - const queryResult = await this.conn.query(`SELECT * FROM ${table}`); - return queryResult.rows.map((row) => ({ - id: row.id, - value: JSON.parse(row.value) - })); - } - - async getRowByKey(table, key) { - this.checkConnection(); - const queryResult = await this.conn.query(`SELECT value FROM ${table} WHERE id = $1`, [key]); - - if (queryResult.rowCount < 1) return [null, false]; - return [JSON.parse(queryResult.rows[0].value), true]; - } - - async setRowByKey(table, key, value, update) { - this.checkConnection(); - - const stringifiedValue = JSON.stringify(value); - - if (update) { - await this.conn.query(`UPDATE ${table} SET value = $1 WHERE id = $2`, [stringifiedValue, key]); - } else { - await this.conn.query(`INSERT INTO ${table} (id, value) VALUES ($1, $2)`, [key, stringifiedValue]); - } - - return value; - } - - async deleteAllRows(table) { - this.checkConnection(); - const queryResult = await this.conn.query(`DELETE FROM ${table}`); - return queryResult.rowCount; - } - - async deleteRowByKey(table, key) { - this.checkConnection(); - const queryResult = await this.conn.query(`DELETE FROM ${table} WHERE id = $1`, [key]); - return queryResult.rowCount; - } -}; diff --git a/src/utils/pgsqlDriver.ts b/src/utils/pgsqlDriver.ts new file mode 100644 index 00000000..361f2486 --- /dev/null +++ b/src/utils/pgsqlDriver.ts @@ -0,0 +1,111 @@ +/** + * Official pre-released quick.db Postgresql driver. Remove this file when the driver is officially released ~ zhiyan114 + * Why add this? PostgreSQL is a better database than MySQL. Alternative would be to use Prisma, + * but I don't want to esentially re-write the database logic and cause confusions. + * Source: https://github.com/plexidev/quick.db/blob/dev/src/drivers/PostgresDriver.ts + * LICENSE: https://github.com/plexidev/quick.db/blob/dev/LICENSE.md + */ + +// eslint-disable-next-line node/no-missing-require +import { Client, ClientConfig } from "pg"; + +export class PostgresDriver { + private static instance: PostgresDriver; + private config: ClientConfig; + private conn: Client | undefined; + + constructor(config: ClientConfig) { + this.config = config; + } + + static createSingleton(config: ClientConfig): PostgresDriver { + if (!this.instance) this.instance = new PostgresDriver(config); + return this.instance; + } + + async connect(): Promise { + this.conn = new Client(this.config); + await this.conn.connect(); + } + + async disconnect(): Promise { + this.checkConnection(); + await this.conn!.end(); + } + + private checkConnection(): void { + if (!this.conn) { + throw new Error("No connection to postgres database"); + } + } + + async prepare(table: string): Promise { + this.checkConnection(); + await this.conn!.query( + `CREATE TABLE IF NOT EXISTS ${table} (id VARCHAR(255), value TEXT)` + ); + } + + async getAllRows(table: string): Promise<{ id: string; value: any }[]> { + this.checkConnection(); + const queryResult = await this.conn!.query(`SELECT * FROM ${table}`); + return queryResult.rows.map((row) => ({ + id: row.id, + value: JSON.parse(row.value), + })); + } + + async getRowByKey( + table: string, + key: string + ): Promise<[T | null, boolean]> { + this.checkConnection(); + const queryResult = await this.conn!.query( + `SELECT value FROM ${table} WHERE id = $1`, + [key] + ); + + if (queryResult.rowCount < 1) return [null, false]; + return [JSON.parse(queryResult.rows[0].value), true]; + } + + async setRowByKey( + table: string, + key: string, + value: any, + update: boolean + ): Promise { + this.checkConnection(); + + const stringifiedValue = JSON.stringify(value); + + if (update) { + await this.conn!.query( + `UPDATE ${table} SET value = $1 WHERE id = $2`, + [stringifiedValue, key] + ); + } else { + await this.conn!.query( + `INSERT INTO ${table} (id, value) VALUES ($1, $2)`, + [key, stringifiedValue] + ); + } + + return value; + } + + async deleteAllRows(table: string): Promise { + this.checkConnection(); + const queryResult = await this.conn!.query(`DELETE FROM ${table}`); + return queryResult.rowCount; + } + + async deleteRowByKey(table: string, key: string): Promise { + this.checkConnection(); + const queryResult = await this.conn!.query( + `DELETE FROM ${table} WHERE id = $1`, + [key] + ); + return queryResult.rowCount; + } +} \ No newline at end of file From 3682bc45aac1e7d5397d59c6f169b30b0ce895da Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 17:21:12 -0400 Subject: [PATCH 09/52] Converted all commands to ts --- src/Types.ts | 11 +++- src/commands/{add.js => add.ts} | 30 ++++----- src/commands/{claim.js => claim.ts} | 0 src/commands/{close.js => close.ts} | 7 ++- src/commands/{remove.js => remove.ts} | 12 ++-- src/commands/{rename.js => rename.ts} | 15 +++-- src/index.ts | 4 +- src/utils/{logs.js => logs.ts} | 88 +++++++++++++++++++-------- 8 files changed, 103 insertions(+), 64 deletions(-) rename src/commands/{add.js => add.ts} (79%) rename src/commands/{claim.js => claim.ts} (100%) rename src/commands/{close.js => close.ts} (84%) rename src/commands/{remove.js => remove.ts} (79%) rename src/commands/{rename.js => rename.ts} (76%) rename src/utils/{logs.js => logs.ts} (62%) diff --git a/src/Types.ts b/src/Types.ts index 0c4d03e9..0c453490 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -1,5 +1,7 @@ -import { Client, Collection, Interaction, SlashCommandBuilder } from "discord.js"; -import { QuickDB } from "quick.db"; +/* Licensed under Apache License 2.0: https://github.com/Sayrix/Ticket-Bot/blob/typescript/LICENSE */ + +import type { Client, Collection, Interaction, SlashCommandBuilder } from "discord.js"; +import type { QuickDB } from "quick.db"; // Config types and setups type TicketQuestion = { @@ -115,6 +117,9 @@ export type locale = { ticketAlreadyClaimed: string, ticketClaimedMessage: string, ticketOnlyClosableByStaff: string, + ticketOnlyRenamableByStaff: string; + ticketRenamed: string; + noTickets: string; ticketAlreadyClosed: string, ticketCreatingTranscript: string, ticketTranscriptCreated: string, @@ -139,6 +144,6 @@ export interface DiscordClient extends Client { config: config; db: QuickDB; locales: locale; - msToHm: (ms: number) => string; + msToHm: (ms: number | Date) => string; commands: Collection; } diff --git a/src/commands/add.js b/src/commands/add.ts similarity index 79% rename from src/commands/add.js rename to src/commands/add.ts index c4216229..821dedd6 100644 --- a/src/commands/add.js +++ b/src/commands/add.ts @@ -1,4 +1,6 @@ -const { SlashCommandBuilder } = require("discord.js"); +import {CommandInteraction, SlashCommandBuilder, TextChannel} from "discord.js"; +import { DiscordClient } from "../Types"; +import { log } from "../utils/logs"; /* Copyright 2023 Sayrix (github.com/Sayrix) @@ -21,18 +23,18 @@ module.exports = { .setName("add") .setDescription("Add someone to the ticket") .addUserOption((input) => input.setName("user").setDescription("The user to add").setRequired(true)), - async execute(interaction, client) { - const added = interaction.options.getUser("user"); - const ticket = await client.db.get(`tickets_${interaction.channel.id}`); + async execute(interaction: CommandInteraction, client: DiscordClient) { + const added = interaction.options.getUser("user", true); + const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); if (!ticket) return interaction.reply({ content: "Ticket not found", ephemeral: true }).catch((e) => console.log(e)); if (ticket.invited.includes(added.id)) return interaction.reply({ content: "User already added", ephemeral: true }).catch((e) => console.log(e)); if (ticket.invited.lenght >= 25) return interaction.reply({ content: "You can't add more than 25 users", ephemeral: true }).catch((e) => console.log(e)); - client.db.push(`tickets_${interaction.channel.id}.invited`, added.id); + client.db.push(`tickets_${interaction.channel?.id}.invited`, added.id); - await interaction.channel.permissionOverwrites + await (interaction.channel as TextChannel | null)?.permissionOverwrites .edit(added, { SendMessages: true, AddReactions: true, @@ -44,19 +46,13 @@ module.exports = { interaction.reply({ content: `> Added <@${added.id}> to the ticket` }).catch((e) => console.log(e)); - client.log( - "userAdded", + log( { - user: { - tag: interaction.user.tag, - id: interaction.user.id, - avatarURL: interaction.user.displayAvatarURL(), - }, + LogType: "userAdded", + user: interaction.user, ticketId: ticket.id, - ticketChannelId: interaction.channel.id, - added: { - id: added.id, - }, + ticketChannelId: interaction.channel?.id, + target: added, }, client ); diff --git a/src/commands/claim.js b/src/commands/claim.ts similarity index 100% rename from src/commands/claim.js rename to src/commands/claim.ts diff --git a/src/commands/close.js b/src/commands/close.ts similarity index 84% rename from src/commands/close.js rename to src/commands/close.ts index 64182eb8..9bd506a1 100644 --- a/src/commands/close.js +++ b/src/commands/close.ts @@ -1,4 +1,5 @@ -const { SlashCommandBuilder } = require("discord.js"); +import { CommandInteraction, GuildMember, SlashCommandBuilder } from "discord.js"; +import { DiscordClient } from "../Types"; /* Copyright 2023 Sayrix (github.com/Sayrix) @@ -18,10 +19,10 @@ limitations under the License. module.exports = { data: new SlashCommandBuilder().setName("close").setDescription("Close the ticket"), - async execute(interaction, client) { + async execute(interaction: CommandInteraction, client: DiscordClient) { if ( client.config.whoCanCloseTicket === "STAFFONLY" && - !interaction.member.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id)) + !(interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id)) ) return interaction .reply({ diff --git a/src/commands/remove.js b/src/commands/remove.ts similarity index 79% rename from src/commands/remove.js rename to src/commands/remove.ts index 6a0c17fe..e8bf4cd4 100644 --- a/src/commands/remove.js +++ b/src/commands/remove.ts @@ -1,4 +1,5 @@ -const { SlashCommandBuilder, ActionRowBuilder, StringSelectMenuBuilder } = require("discord.js"); +import { SlashCommandBuilder, ActionRowBuilder, StringSelectMenuBuilder, CommandInteraction, User } from "discord.js"; +import { DiscordClient } from "../Types"; /* Copyright 2023 Sayrix (github.com/Sayrix) @@ -18,8 +19,9 @@ limitations under the License. module.exports = { data: new SlashCommandBuilder().setName("remove").setDescription("Remove someone from the ticket"), - async execute(interaction, client) { - const ticket = await client.db.get(`tickets_${interaction.channel.id}`); + // @TODO: Fix type definitions when I figure it out via ORM migration. For now assign a random type that gets the error removed. + async execute(interaction: CommandInteraction, client: DiscordClient) { + const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); if (!ticket) return interaction.reply({ content: "Ticket not found", ephemeral: true }).catch((e) => console.log(e)); if (ticket.invited.length < 1) return interaction.reply({ content: "There are no users to remove", ephemeral: true }).catch((e) => console.log(e)); @@ -27,9 +29,9 @@ module.exports = { await client.users.fetch(ticket.invited[i]); } - const addedUsers = ticket.invited.map((user) => client.users.cache.get(user)); + const addedUsers: User[] = ticket.invited.map((user: string) => client.users.cache.get(user)); - const row = new ActionRowBuilder().addComponents( + const row = new ActionRowBuilder().addComponents( new StringSelectMenuBuilder() .setCustomId("removeUser") .setPlaceholder("Please select a user to remove") diff --git a/src/commands/rename.js b/src/commands/rename.ts similarity index 76% rename from src/commands/rename.js rename to src/commands/rename.ts index eae89085..0a5255b9 100644 --- a/src/commands/rename.js +++ b/src/commands/rename.ts @@ -1,6 +1,5 @@ -const { SlashCommandBuilder } = require("discord.js"); -// eslint-disable-next-line no-unused-vars -const Discord = require("discord.js"); +import { CommandInteraction, GuildMember, SlashCommandBuilder, TextChannel } from 'discord.js'; +import { DiscordClient } from '../Types'; /* Copyright 2023 Sayrix (github.com/Sayrix) @@ -29,10 +28,10 @@ module.exports = { * @param {Discord.Client} client * @returns */ - async execute(interaction, client) { - const ticket = await client.db.get(`tickets_${interaction.channel.id}`); + async execute(interaction: CommandInteraction, client: DiscordClient) { + const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); if (!ticket) return interaction.reply({ content: "Ticket not found", ephemeral: true }).catch((e) => console.log(e)); - if (!interaction.member.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id))) + if (!(interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id))) return interaction .reply({ content: client.locales.ticketOnlyRenamableByStaff, @@ -40,9 +39,9 @@ module.exports = { }) .catch((e) => console.log(e)); - interaction.channel.setName(interaction.options.getString("name")).catch((e) => console.log(e)); + (interaction.channel as TextChannel)?.setName(interaction.options.get("name", true).value as string).catch((e) => console.log(e)); interaction - .reply({ content: client.locales.ticketRenamed.replace("NEWNAME", interaction.channel.toString()), ephemeral: false }) + .reply({ content: client.locales.ticketRenamed.replace("NEWNAME", (interaction.channel as TextChannel | null)?.toString() ?? "Unknown"), ephemeral: false }) .catch((e) => console.log(e)); }, }; diff --git a/src/index.ts b/src/index.ts index 47604132..e9a486fa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -137,7 +137,9 @@ if (client.config.postgre?.enabled) { } client.locales = require(`./locales/${client.config.lang}.json`) as locale; -client.msToHm = function dhm(ms: number) { +client.msToHm = function dhm(ms: number | Date) { + if(ms instanceof Date) ms = ms.getTime(); + const days = Math.floor(ms / (24 * 60 * 60 * 1000)); const daysms = ms % (24 * 60 * 60 * 1000); const hours = Math.floor(daysms / (60 * 60 * 1000)); diff --git a/src/utils/logs.js b/src/utils/logs.ts similarity index 62% rename from src/utils/logs.js rename to src/utils/logs.ts index 373214a8..3521b059 100644 --- a/src/utils/logs.js +++ b/src/utils/logs.ts @@ -1,4 +1,5 @@ -const Discord = require("discord.js"); +import Discord, { ChannelType, TextChannel, User } from "discord.js"; +import { DiscordClient } from "../Types"; /* Copyright 2023 Sayrix (github.com/Sayrix) @@ -16,22 +17,56 @@ See the License for the specific language governing permissions and limitations under the License. */ -module.exports = { - async log(logsType, logs, client) { +type log = { + LogType: "ticketCreate" + user: User + ticketChannelId?: string; + reason: string; +} | { + LogType: "ticketClaim" | "ticketClose" + user: User + ticketChannelId?: string; + ticketId?: string; + reason?: string; + ticketCreatedAt: number; +} | { + LogType: "ticketDelete" + user: User + ticketChannelId?: string; + ticketId?: string; + reason?: string; + ticketCreatedAt: number; + transcriptURL?: string; + +} | { + LogType: "userAdded" | "userRemoved" + user: User; + target: User; + ticketChannelId?: string; + reason?: string; + ticketId?: string; +} + + +export const log = async(logs: log, client: DiscordClient) => { if (!client.config.logs) return; if (!client.config.logsChannelId) return; const channel = await client.channels .fetch(client.config.logsChannelId) .catch((e) => console.error("The channel to log events is not found!\n", e)); if (!channel) return console.error("The channel to log events is not found!"); + if (!channel.isTextBased() || + channel.type === ChannelType.DM || + channel.type === ChannelType.PrivateThread || + channel.type === ChannelType.PublicThread) return console.error("Invalid Channel!"); - const webhook = (await channel.fetchWebhooks()).find((wh) => wh.token) ?? - await channel.createWebhook({ name: "Ticket Bot Logs" }); + const webhook = (await (channel as TextChannel).fetchWebhooks()).find((wh) => wh.token) ?? + await (channel as TextChannel).createWebhook({ name: "Ticket Bot Logs" }); - if (logsType === "ticketCreate") { + if (logs.LogType === "ticketCreate") { const embed = new Discord.EmbedBuilder() - .setColor("3ba55c") - .setAuthor({ name: logs.user.tag, iconURL: logs.user.avatarURL }) + .setColor("#3ba55c") + .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) .setDescription(`${logs.user.tag} (<@${logs.user.id}>) Created a ticket (<#${logs.ticketChannelId}>) with the reason: \`${logs.reason}\``); webhook @@ -43,10 +78,10 @@ module.exports = { .catch((e) => console.log(e)); } - if (logsType === "ticketClaim") { + if (logs.LogType === "ticketClaim") { const embed = new Discord.EmbedBuilder() - .setColor("faa61a") - .setAuthor({ name: logs.user.tag, iconURL: logs.user.avatarURL }) + .setColor("#faa61a") + .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) .setDescription( `${logs.user.tag} (<@${logs.user.id}>) Claimed the ticket n°${logs.ticketId} (<#${logs.ticketChannelId}>) after ${client.msToHm( new Date(Date.now() - logs.ticketCreatedAt) @@ -62,10 +97,10 @@ module.exports = { .catch((e) => console.log(e)); } - if (logsType === "ticketClose") { + if (logs.LogType === "ticketClose") { const embed = new Discord.EmbedBuilder() - .setColor("ed4245") - .setAuthor({ name: logs.user.tag, iconURL: logs.user.avatarURL }) + .setColor("#ed4245") + .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) .setDescription( `${logs.user.tag} (<@${logs.user.id}>) Closed the ticket n°${logs.ticketId} (<#${logs.ticketChannelId}>) with the reason: \`${ logs.reason @@ -81,10 +116,10 @@ module.exports = { .catch((e) => console.log(e)); } - if (logsType === "ticketDelete") { + if (logs.LogType === "ticketDelete") { const embed = new Discord.EmbedBuilder() - .setColor("ed4245") - .setAuthor({ name: logs.user.tag, iconURL: logs.user.avatarURL }) + .setColor("#ed4245") + .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) .setDescription( `${logs.user.tag} (<@${logs.user.id}>) Deleted the ticket n°${logs.ticketId} after ${client.msToHm( new Date(Date.now() - logs.ticketCreatedAt) @@ -100,12 +135,12 @@ module.exports = { .catch((e) => console.log(e)); } - if (logsType === "userAdded") { + if (logs.LogType === "userAdded") { const embed = new Discord.EmbedBuilder() - .setColor("3ba55c") - .setAuthor({ name: logs.user.tag, iconURL: logs.user.avatarURL }) + .setColor("#3ba55c") + .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) .setDescription( - `${logs.user.tag} (<@${logs.user.id}>) Added <@${logs.added.id}> (${logs.added.id}) to the ticket n°${logs.ticketId} (<#${logs.ticketChannelId}>)` + `${logs.user.tag} (<@${logs.user.id}>) Added <@${logs.target.id}> (${logs.target.id}) to the ticket n°${logs.ticketId} (<#${logs.ticketChannelId}>)` ); webhook @@ -117,12 +152,12 @@ module.exports = { .catch((e) => console.log(e)); } - if (logsType === "userRemoved") { + if (logs.LogType === "userRemoved") { const embed = new Discord.EmbedBuilder() - .setColor("ed4245") - .setAuthor({ name: logs.user.tag, iconURL: logs.user.avatarURL }) + .setColor("#ed4245") + .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) .setDescription( - `${logs.user.tag} (<@${logs.user.id}>) Removed <@${logs.removed.id}> (${logs.removed.id}) from the ticket n°${logs.ticketId} (<#${logs.ticketChannelId}>)` + `${logs.user.tag} (<@${logs.user.id}>) Removed <@${logs.target.id}> (${logs.target.id}) from the ticket n°${logs.ticketId} (<#${logs.ticketChannelId}>)` ); webhook @@ -133,8 +168,7 @@ module.exports = { }) .catch((e) => console.log(e)); } - }, -}; + } /* Copyright 2023 Sayrix (github.com/Sayrix) From c77fa0c67fc5d09a30c77b28e0d3493697d6ac69 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 18:58:21 -0400 Subject: [PATCH 10/52] Added builder to support build test --- .github/workflows/{eslint.yml => builder.yml} | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) rename .github/workflows/{eslint.yml => builder.yml} (91%) diff --git a/.github/workflows/eslint.yml b/.github/workflows/builder.yml similarity index 91% rename from .github/workflows/eslint.yml rename to .github/workflows/builder.yml index 5e2af47f..817c871c 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/builder.yml @@ -1,4 +1,4 @@ -name: ESLint Check +name: Source Check on: push: @@ -40,3 +40,6 @@ jobs: - name: ESLint run: npx --no-install eslint . + + - name: build + run: npm run build --if-present From a3a33af4d9dc962a0b7248d21a89696a899a335d Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 20:18:02 -0400 Subject: [PATCH 11/52] Inital Migration for #158; need more work for corrections --- src/Types.ts | 17 +- src/commands/claim.ts | 5 +- src/commands/remove.ts | 5 +- ...eractionCreate.js => interactionCreate.ts} | 56 ++-- src/events/{ready.js => ready.ts} | 0 src/utils/claim.js | 116 -------- src/utils/claim.ts | 112 ++++++++ src/utils/close.js | 270 ------------------ src/utils/close.ts | 266 +++++++++++++++++ ...{close_askReason.js => close_askReason.ts} | 57 ++-- src/utils/createTicket.js | 253 ---------------- src/utils/createTicket.ts | 251 ++++++++++++++++ src/utils/{delete.js => delete.ts} | 44 ++- src/utils/logs.ts | 6 +- src/utils/test.ts | 18 ++ 15 files changed, 742 insertions(+), 734 deletions(-) rename src/events/{interactionCreate.js => interactionCreate.ts} (81%) rename src/events/{ready.js => ready.ts} (100%) delete mode 100644 src/utils/claim.js create mode 100644 src/utils/claim.ts delete mode 100644 src/utils/close.js create mode 100644 src/utils/close.ts rename src/utils/{close_askReason.js => close_askReason.ts} (50%) delete mode 100644 src/utils/createTicket.js create mode 100644 src/utils/createTicket.ts rename src/utils/{delete.js => delete.ts} (62%) create mode 100644 src/utils/test.ts diff --git a/src/Types.ts b/src/Types.ts index 0c453490..189ef43b 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -10,15 +10,17 @@ type TicketQuestion = { style: string; maxLength: number; } -type TicketType = { +export type TicketType = { codeName: string; name: string; description: string; + emoji: string; + color: string; categoryId: string; ticketNameOption: string; customDescription: string; cantAccess: string[]; - askQuestion: boolean; + askQuestions: boolean; questions: TicketQuestion[]; } type dbConn = { @@ -32,14 +34,14 @@ type dbConn = { export type config = { clientId: string; guildId: string; - maincolor: string; + mainColor: string; lang: string; // Tho can be cs/de/es/fr/main/tr type but we can't guarantee what users put // Database credentials are deprecated, will be removed when Prisma are in-place. postgre?: dbConn; mysql?: dbConn; closeTicketCategoryId: string; openTicketChannelId: string; - TicketTypes: TicketType[]; + ticketTypes: TicketType[]; ticketNameOption: string; ticketNamePrefixWhenClaimed: string; rolesWhoHaveAccessToTheTickets: string[]; @@ -75,7 +77,8 @@ export type locale = { title: string, description: string, footer: { - text: string + text: string, + iconUrl?: string } }, ticketClosed: { @@ -84,9 +87,11 @@ export type locale = { }, ticketClosedDM: { title: string, + color?: string, description: string, footer: { - text: string + text: string, + iconUrl?: string } } }, diff --git a/src/commands/claim.ts b/src/commands/claim.ts index f9d80673..fe19f127 100644 --- a/src/commands/claim.ts +++ b/src/commands/claim.ts @@ -1,4 +1,5 @@ -const { SlashCommandBuilder } = require("discord.js"); +import { CommandInteraction, SlashCommandBuilder } from "discord.js"; +import { DiscordClient } from "../Types"; /* Copyright 2023 Sayrix (github.com/Sayrix) @@ -18,7 +19,7 @@ limitations under the License. module.exports = { data: new SlashCommandBuilder().setName("claim").setDescription("Set the ticket as claimed."), - async execute(interaction, client) { + async execute(interaction: CommandInteraction, client: DiscordClient) { const { claim } = require("../utils/claim.js"); claim(interaction, client); }, diff --git a/src/commands/remove.ts b/src/commands/remove.ts index e8bf4cd4..07aa9927 100644 --- a/src/commands/remove.ts +++ b/src/commands/remove.ts @@ -19,7 +19,7 @@ limitations under the License. module.exports = { data: new SlashCommandBuilder().setName("remove").setDescription("Remove someone from the ticket"), - // @TODO: Fix type definitions when I figure it out via ORM migration. For now assign a random type that gets the error removed. + async execute(interaction: CommandInteraction, client: DiscordClient) { const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); if (!ticket) return interaction.reply({ content: "Ticket not found", ephemeral: true }).catch((e) => console.log(e)); @@ -28,7 +28,7 @@ module.exports = { for (let i = 0; i < ticket.invited.length; i++) { await client.users.fetch(ticket.invited[i]); } - + // @TODO: Fix type definitions when I figure it out via ORM migration. For now assign a random type that gets the error removed. const addedUsers: User[] = ticket.invited.map((user: string) => client.users.cache.get(user)); const row = new ActionRowBuilder().addComponents( @@ -38,6 +38,7 @@ module.exports = { .setMinValues(1) .setMaxValues(ticket.invited.length) .addOptions( + // @TODO: Fix type definitions when I figure it out via ORM migration. For now assign a random type that gets the error removed. addedUsers.map((user) => { return { label: user.tag, diff --git a/src/events/interactionCreate.js b/src/events/interactionCreate.ts similarity index 81% rename from src/events/interactionCreate.js rename to src/events/interactionCreate.ts index 039a49c5..7b16b2dc 100644 --- a/src/events/interactionCreate.js +++ b/src/events/interactionCreate.ts @@ -1,4 +1,8 @@ -const Discord = require("discord.js"); +import { ActionRowBuilder, GuildChannel, GuildMember, Interaction, ModalBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; +import { DiscordClient } from "../Types"; +import { log } from "../utils/logs"; +import {createTicket} from '../utils/createTicket'; +import { close } from '../utils/close'; /* Copyright 2023 Sayrix (github.com/Sayrix) @@ -23,7 +27,7 @@ module.exports = { * @param {Discord.Interaction} interaction * @param {Discord.Client} client */ - async execute(interaction, client) { + async execute(interaction: Interaction, client: DiscordClient) { if (interaction.isButton()) { if (interaction.customId === "openTicket") { await interaction.deferReply({ ephemeral: true }).catch((e) => console.log(e)); @@ -31,11 +35,10 @@ module.exports = { // Max ticket opened for (let role of client.config.rolesWhoCanNotCreateTickets) { - if (role && interaction.member.roles.cache.has(role)) { + if (role && (interaction.member as GuildMember | null)?.roles.cache.has(role)) { return interaction .editReply({ - content: "You can't create a ticket because you are blacklisted", - ephemeral: true, + content: "You can't create a ticket because you are blacklisted" }) .catch((e) => console.log(e)); } @@ -48,8 +51,7 @@ module.exports = { if (ticketsOpened > client.config.maxTicketOpened || ticketsOpened === client.config.maxTicketOpened) { return interaction .editReply({ - content: client.locales.ticketLimitReached.replace("TICKETLIMIT", client.config.maxTicketOpened), - ephemeral: true, + content: client.locales.ticketLimitReached.replace("TICKETLIMIT", client.config.maxTicketOpened.toString()) }) .catch((e) => console.log(e)); } @@ -63,7 +65,7 @@ module.exports = { // x.cantAccess is an array of roles id // If the user has one of the roles, he can't access to this ticket type - const a = { + const a: {[key: string]: string} = { label: x.name, value: x.codeName, }; @@ -76,7 +78,7 @@ module.exports = { let option = client.config.ticketTypes.filter((y) => y.codeName === x.value)[0]; if (option.cantAccess) { for (let role of option.cantAccess) { - if (role && interaction.member.roles.cache.has(role)) { + if (role && (interaction.member as GuildMember | null)?.roles.cache.has(role)) { options = options.filter((y) => y.value !== x.value); } } @@ -84,7 +86,6 @@ module.exports = { } if (options.length <= 0) return interaction.editReply({ - ephemeral: true, content: client.locales.noTickets }); @@ -98,7 +99,6 @@ module.exports = { interaction .editReply({ - ephemeral: true, components: [row], }) .catch((e) => console.log(e)); @@ -135,7 +135,7 @@ module.exports = { if (ticketsOpened > client.config.maxTicketOpened || ticketsOpened === client.config.maxTicketOpened) { return interaction .reply({ - content: client.locales.ticketLimitReached.replace("TICKETLIMIT", client.config.maxTicketOpened), + content: client.locales.ticketLimitReached.replace("TICKETLIMIT", client.config.maxTicketOpened.toString()), ephemeral: true, }) .catch((e) => console.log(e)); @@ -145,17 +145,17 @@ module.exports = { const ticketType = client.config.ticketTypes.find((x) => x.codeName === interaction.values[0]); if (!ticketType) return console.error(`Ticket type ${interaction.values[0]} not found!`); if (ticketType.askQuestions) { - const modal = new client.discord.ModalBuilder().setCustomId("askReason").setTitle(client.locales.modals.reasonTicketOpen.title); + const modal = new ModalBuilder().setCustomId("askReason").setTitle(client.locales.modals.reasonTicketOpen.title); ticketType.questions.forEach((x, i) => { - const input = new client.discord.TextInputBuilder() + const input = new TextInputBuilder() .setCustomId(`input_${interaction.values[0]}_${i}`) .setLabel(x.label) - .setStyle(x.style == "SHORT" ? client.discord.TextInputStyle.Short : client.discord.TextInputStyle.Paragraph) + .setStyle(x.style == "SHORT" ? TextInputStyle.Short : TextInputStyle.Paragraph) .setPlaceholder(x.placeholder) .setMaxLength(x.maxLength); - const firstActionRow = new client.discord.ActionRowBuilder().addComponents(input); + const firstActionRow = new ActionRowBuilder().addComponents(input); modal.addComponents(firstActionRow); }); @@ -170,19 +170,15 @@ module.exports = { client.db.pull(`tickets_${interaction.message.channel.id}.invited`, interaction.values); interaction.values.forEach((value) => { - interaction.channel.permissionOverwrites.delete(value).catch((e) => console.log(e)); + (interaction.channel as GuildChannel | null)?.permissionOverwrites.delete(value).catch((e) => console.log(e)); - client.log( - "userRemoved", + log( { - user: { - tag: interaction.user.tag, - id: interaction.user.id, - avatarURL: interaction.user.displayAvatarURL(), - }, + LogType: "userRemoved", + user: interaction.user, ticketId: ticket.id, - ticketChannelId: interaction.channel.id, - removed: { + ticketChannelId: interaction.channel?.id, + target: { id: value, }, }, @@ -203,16 +199,16 @@ module.exports = { if (interaction.isModalSubmit()) { if (interaction.customId === "askReason") { - const type = interaction.fields.fields.first().customId.split("_")[1]; + const type = interaction.fields.fields.first()?.customId.split("_")[1]; const ticketType = client.config.ticketTypes.find((x) => x.codeName === type); + //@ts-ignore Remove illegal usages without breaking compatibility if (!ticketType) return console.error(`Ticket type ${interaction.values[0]} not found!`); - require("../utils/createTicket.js").createTicket(interaction, client, ticketType, interaction.fields.fields); + createTicket(interaction, client, ticketType, interaction.fields.fields); } if (interaction.customId === "askReasonClose") { await interaction.deferReply().catch((e) => console.log(e)); - const { close } = require("../utils/close.js"); - close(interaction, client, interaction.fields.fields.first().value); + close(interaction, client, interaction.fields.fields.first()?.value); } } }, diff --git a/src/events/ready.js b/src/events/ready.ts similarity index 100% rename from src/events/ready.js rename to src/events/ready.ts diff --git a/src/utils/claim.js b/src/utils/claim.js deleted file mode 100644 index 54fc6d5c..00000000 --- a/src/utils/claim.js +++ /dev/null @@ -1,116 +0,0 @@ -// eslint-disable-next-line no-unused-vars -const Discord = require("discord.js"); - -/* -Copyright 2023 Sayrix (github.com/Sayrix) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -module.exports = { - /** - * @param {Discord.CommandInteraction} interaction - * @param {Discord.Client} client - */ - async claim(interaction, client) { - const ticket = await client.db.get(`tickets_${interaction.channel.id}`); - if (!ticket) - return interaction.reply({ - content: "Ticket not found", - ephemeral: true, - }); - - const canClaim = interaction.member.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id)); - - if (!canClaim) - return interaction - .reply({ - content: client.locales.ticketOnlyClaimableByStaff, - ephemeral: true, - }) - .catch((e) => console.log(e)); - - if (ticket.claimed) - return interaction - .reply({ - content: client.locales.ticketAlreadyClaimed, - ephemeral: true, - }) - .catch((e) => console.log(e)); - - client.log( - "ticketClaim", - { - user: { - tag: interaction.user.tag, - id: interaction.user.id, - avatarURL: interaction.user.displayAvatarURL(), - }, - ticketId: ticket.id, - ticketChannelId: interaction.channel.id, - ticketCreatedAt: ticket.createdAt, - }, - client - ); - - await client.db.set(`tickets_${interaction.channel.id}.claimed`, true); - await client.db.set(`tickets_${interaction.channel.id}.claimedBy`, interaction.user.id); - await client.db.set(`tickets_${interaction.channel.id}.claimedAt`, Date.now()); - - await interaction.channel.messages.fetch(); - const messageId = await client.db.get(`tickets_${interaction.channel.id}.messageId`); - const msg = interaction.channel.messages.cache.get(messageId); - - const embed = msg.embeds[0].data; - embed.description = embed.description + `\n\n ${client.locales.other.claimedBy.replace("USER", `<@${interaction.user.id}>`)}`; - - msg.components[0].components.map((x) => { - if (x.data.custom_id === "claim") x.data.disabled = true; - }); - - msg - .edit({ - content: msg.content, - embeds: [embed], - components: msg.components, - }) - .catch((e) => console.log(e)); - - interaction - .reply({ - content: client.locales.ticketClaimedMessage.replace("USER", `<@${interaction.user.id}>`), - ephemeral: false, - }) - .catch((e) => console.log(e)); - - if (client.config.ticketNamePrefixWhenClaimed) { - interaction.channel.setName(`${client.config.ticketNamePrefixWhenClaimed}${interaction.channel.name}`).catch((e) => console.log(e)); - } - }, -}; - -/* -Copyright 2023 Sayrix (github.com/Sayrix) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ diff --git a/src/utils/claim.ts b/src/utils/claim.ts new file mode 100644 index 00000000..da7b8595 --- /dev/null +++ b/src/utils/claim.ts @@ -0,0 +1,112 @@ +/* +Copyright 2023 Sayrix (github.com/Sayrix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { ButtonInteraction, GuildMember, TextChannel } from "discord.js"; +import { DiscordClient } from "../Types"; +import { log } from "./logs"; + + +/** +* @param {Discord.CommandInteraction} interaction +* @param {Discord.Client} client +*/ +export const claim = async(interaction: ButtonInteraction, client: DiscordClient) => { + const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); + if (!ticket) + return interaction.reply({ + content: "Ticket not found", + ephemeral: true, + }); + + const canClaim = (interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id)); + + if (!canClaim) + return interaction + .reply({ + content: client.locales.ticketOnlyClaimableByStaff, + ephemeral: true, + }) + .catch((e) => console.log(e)); + + if (ticket.claimed) + return interaction + .reply({ + content: client.locales.ticketAlreadyClaimed, + ephemeral: true, + }) + .catch((e) => console.log(e)); + + log( + { + LogType: "ticketClaim", + user: interaction.user, + ticketId: ticket.id, + ticketChannelId: interaction.channel?.id, + ticketCreatedAt: ticket.createdAt, + }, + client + ); + + await client.db.set(`tickets_${interaction.channel?.id}.claimed`, true); + await client.db.set(`tickets_${interaction.channel?.id}.claimedBy`, interaction.user.id); + await client.db.set(`tickets_${interaction.channel?.id}.claimedAt`, Date.now()); + + //await interaction.channel?.messages.fetch(); // Commented bc it seems useless + const messageId = await client.db.get(`tickets_${interaction.channel?.id}.messageId`); + const msg = interaction.channel?.messages.cache.get(messageId); + const embed = msg?.embeds[0].data; + //@ts-ignore TODO: Remove this illegal usage without breaking code + embed.description = embed?.description + `\n\n ${client.locales.other.claimedBy.replace("USER", `<@${interaction.user.id}>`)}`; + + msg?.components[0].components.map((x) => { + //@ts-ignore TODO: Remove this illegal usage without breaking code + if (x.data.custom_id === "claim") x.data.disabled = true; + }); + + msg?.edit({ + content: msg.content, + //@ts-ignore TODO: Remove this illegal usage without breaking code + embeds: [embed], + components: msg.components, + }) + .catch((e) => console.log(e)); + + interaction + .reply({ + content: client.locales.ticketClaimedMessage.replace("USER", `<@${interaction.user.id}>`), + ephemeral: false, + }) + .catch((e) => console.log(e)); + + if (client.config.ticketNamePrefixWhenClaimed) { + (interaction.channel as TextChannel | null)?.setName(`${client.config.ticketNamePrefixWhenClaimed}${(interaction.channel as TextChannel | null)?.name}`).catch((e) => console.log(e)); + } +} +/* +Copyright 2023 Sayrix (github.com/Sayrix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ diff --git a/src/utils/close.js b/src/utils/close.js deleted file mode 100644 index 59494ec0..00000000 --- a/src/utils/close.js +++ /dev/null @@ -1,270 +0,0 @@ -const { generateMessages } = require("ticket-bot-transcript-uploader"); -const zlib = require("zlib"); -const axios = require("axios"); -const Discord = require("discord.js"); -let domain = "https://ticket.pm/"; - -/* -Copyright 2023 Sayrix (github.com/Sayrix) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -module.exports = { - async close(interaction, client, reason) { - if (!client.config.createTranscript) domain = client.locales.other.unavailable; - - const ticket = await client.db.get(`tickets_${interaction.channel.id}`); - if (!ticket) return interaction.editReply({ content: "Ticket not found" }).catch((e) => console.log(e)); - - if ( - client.config.whoCanCloseTicket === "STAFFONLY" && - !interaction.member.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id)) - ) - return interaction - .editReply({ - content: client.locales.ticketOnlyClosableByStaff, - ephemeral: true - }) - .catch((e) => console.log(e)); - - if (ticket.closed) - return interaction - .editReply({ - content: client.locales.ticketAlreadyClosed, - ephemeral: true - }) - .catch((e) => console.log(e)); - - client.log( - "ticketClose", - { - user: { - tag: interaction.user.tag, - id: interaction.user.id, - avatarURL: interaction.user.displayAvatarURL() - }, - ticketId: ticket.id, - ticketChannelId: interaction.channel.id, - ticketCreatedAt: ticket.createdAt, - reason: reason - }, - client - ); - - await client.db.set(`tickets_${interaction.channel.id}.closedBy`, interaction.user.id); - await client.db.set(`tickets_${interaction.channel.id}.closedAt`, Date.now()); - - if (reason) { - await client.db.set(`tickets_${interaction.channel.id}.closeReason`, reason); - } else { - await client.db.set(`tickets_${interaction.channel.id}.closeReason`, client.locales.other.noReasonGiven); - } - - const creator = await client.db.get(`tickets_${interaction.channel.id}.creator`); - const invited = await client.db.get(`tickets_${interaction.channel.id}.invited`); - - interaction.channel.permissionOverwrites - .edit(creator, { - ViewChannel: false - }) - .catch((e) => console.log(e)); - - invited.forEach(async (user) => { - interaction.channel.permissionOverwrites - .edit(user, { - ViewChannel: false - }) - .catch((e) => console.log(e)); - }); - - interaction - .editReply({ - content: client.locales.ticketCreatingTranscript - }) - .catch((e) => console.log(e)); - - await interaction.channel.messages.fetch(); - - async function close(id) { - if (client.config.closeTicketCategoryId) interaction.channel.setParent(client.config.closeTicketCategoryId).catch((e) => console.log(e)); - - const messageId = await client.db.get(`tickets_${interaction.channel.id}.messageId`); - const msg = interaction.channel.messages.cache.get(messageId); - const embed = msg.embeds[0].data; - - msg.components[0]?.components?.map((x) => { - if (x.data.custom_id === "close") x.data.disabled = true; - if (x.data.custom_id === "close_askReason") x.data.disabled = true; - }); - - msg - .edit({ - content: msg.content, - embeds: [embed], - components: msg.components - }) - .catch((e) => console.log(e)); - - await client.db.set(`tickets_${interaction.channel.id}.closed`, true); - - interaction.channel - .send({ - content: client.locales.ticketTranscriptCreated.replace( - "TRANSCRIPTURL", - domain === client.locales.other.unavailable ? client.locales.other.unavailable : `<${domain}${id}>` - ) - }) - .catch((e) => console.log(e)); - await client.db.set( - `tickets_${interaction.channel.id}.transcriptURL`, - domain === client.locales.other.unavailable ? client.locales.other.unavailable : `${domain}${id}` - ); - const ticket = await client.db.get(`tickets_${interaction.channel.id}`); - - const row = new Discord.ActionRowBuilder().addComponents( - new Discord.ButtonBuilder().setCustomId("deleteTicket").setLabel(client.locales.other.deleteTicketButtonMSG).setStyle(Discord.ButtonStyle.Danger) - ); - - interaction.channel - .send({ - embeds: [ - JSON.parse( - JSON.stringify(client.locales.embeds.ticketClosed) - .replace("TICKETCOUNT", ticket.id) - .replace("REASON", ticket.closeReason.replace(/[\n\r]/g, "\\n")) - .replace("CLOSERNAME", interaction.user.tag) - ) - ], - components: [row] - }) - .catch((e) => console.log(e)); - - const tiketClosedDMEmbed = new Discord.EmbedBuilder() - .setColor(client.embeds.ticketClosedDM.color ? client.embeds.ticketClosedDM.color : client.config.mainColor) - .setDescription( - client.embeds.ticketClosedDM.description - .replace("TICKETCOUNT", ticket.id) - .replace("TRANSCRIPTURL", `[\`${domain}${id}\`](${domain}${id})`) - .replace("REASON", ticket.closeReason) - .replace("CLOSERNAME", interaction.user.tag) - ) - - /* - Copyright 2023 Sayrix (github.com/Sayrix) - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - - .setFooter({ - // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) - text: "ticket.pm" + client.embeds.ticketClosedDM.footer.text.replace("ticket.pm", ""), // Please respect the LICENSE :D - // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) - iconUrl: client.embeds.ticketClosedDM.footer.iconUrl - }); - - /* - Copyright 2023 Sayrix (github.com/Sayrix) - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - - client.users.fetch(creator).then((user) => { - user - .send({ - embeds: [tiketClosedDMEmbed] - }) - .catch((e) => console.log(e)); - }); - } - - if (!client.config.createTranscript) { - close(""); - return; - } - - async function fetchAll() { - let collArray = new Array(); - let lastID = interaction.channel.lastMessageID; - // eslint-disable-next-line no-constant-condition - while (true) { - const fetched = await interaction.channel.messages.fetch({ limit: 100, before: lastID }); - if (fetched.size === 0) { - break; - } - collArray.push(fetched); - lastID = fetched.last().id; - if (fetched.size !== 100) { - break; - } - } - const messages = collArray[0].concat(...collArray.slice(1)); - return messages; - } - - const messages = await fetchAll(); - const premiumKey = ""; - - const messagesJSON = await generateMessages(messages, premiumKey, "https://m.ticket.pm"); - zlib.gzip(JSON.stringify(messagesJSON), async (err, compressed) => { - if (err) { - console.error(err); - } else { - const ts = await axios - .post(`${domain}upload?key=${premiumKey}`, JSON.stringify(compressed), { - headers: { - "Content-Type": "application/json" - } - }) - .catch(console.error); - close(ts.data); - } - }); - } -}; - -/* -Copyright 2023 Sayrix (github.com/Sayrix) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ diff --git a/src/utils/close.ts b/src/utils/close.ts new file mode 100644 index 00000000..0a5b0207 --- /dev/null +++ b/src/utils/close.ts @@ -0,0 +1,266 @@ +import { generateMessages } from "ticket-bot-transcript-uploader"; +import zlib from "zlib"; +import axios from "axios"; +import { Collection, GuildMember, Message, ModalSubmitInteraction, TextBasedChannel, TextChannel, TextChannelType } from "discord.js"; +import { DiscordClient } from "../Types"; +import { log } from "./logs"; +let domain = "https://ticket.pm/"; + +/* +Copyright 2023 Sayrix (github.com/Sayrix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +export async function close(interaction: ModalSubmitInteraction, client: DiscordClient, reason?: string) { + if (!client.config.createTranscript) domain = client.locales.other.unavailable; + + const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); + if (!ticket) return interaction.editReply({ content: "Ticket not found" }).catch((e) => console.log(e)); + + if ( + client.config.whoCanCloseTicket === "STAFFONLY" && + !(interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id)) + ) + return interaction + .editReply({ + content: client.locales.ticketOnlyClosableByStaff + }) + .catch((e) => console.log(e)); + + if (ticket.closed) + return interaction + .editReply({ + content: client.locales.ticketAlreadyClosed + }) + .catch((e) => console.log(e)); + + log( + { + LogType: "ticketClose", + user: interaction.user, + ticketId: ticket.id, + ticketChannelId: interaction.channel?.id, + ticketCreatedAt: ticket.createdAt, + reason: reason + }, + client + ); + + await client.db.set(`tickets_${interaction.channel?.id}.closedBy`, interaction.user.id); + await client.db.set(`tickets_${interaction.channel?.id}.closedAt`, Date.now()); + + if (reason) { + await client.db.set(`tickets_${interaction.channel?.id}.closeReason`, reason); + } else { + await client.db.set(`tickets_${interaction.channel?.id}.closeReason`, client.locales.other.noReasonGiven); + } + + const creator = await client.db.get(`tickets_${interaction.channel?.id}.creator`); + const invited = await client.db.get(`tickets_${interaction.channel?.id}.invited`); + + (interaction.channel as TextChannel | null)?.permissionOverwrites + .edit(creator, { + ViewChannel: false + }) + .catch((e: unknown) => console.log(e)); + // TODO: Replace user: string with the proper type once ORM is implemented + invited.forEach(async (user: string) => { + (interaction.channel as TextChannel | null)?.permissionOverwrites + .edit(user, { + ViewChannel: false + }) + .catch((e) => console.log(e)); + }); + + interaction + .editReply({ + content: client.locales.ticketCreatingTranscript + }) + .catch((e) => console.log(e)); + + await interaction.channel?.messages.fetch(); + + async function close(id: string) { + if (client.config.closeTicketCategoryId) (interaction.channel as TextChannel | null)?.setParent(client.config.closeTicketCategoryId).catch((e) => console.log(e)); + + const messageId = await client.db.get(`tickets_${interaction.channel?.id}.messageId`); + const msg = interaction.channel?.messages.cache.get(messageId); + const embed = msg?.embeds[0].data; + + msg?.components[0]?.components?.map((x) => { + //@ts-ignore TODO: Remove this illegal usage without breaking code + if (x.data.custom_id === "close") x.data.disabled = true; + //@ts-ignore TODO: Remove this illegal usage without breaking code + if (x.data.custom_id === "close_askReason") x.data.disabled = true; + }); + + msg?.edit({ + content: msg.content, + //@ts-ignore TODO: Remove this illegal usage without breaking code + embeds: [embed], + components: msg.components + }) + .catch((e) => console.log(e)); + + await client.db.set(`tickets_${interaction.channel?.id}.closed`, true); + + interaction.channel?.send({ + content: client.locales.ticketTranscriptCreated.replace( + "TRANSCRIPTURL", + domain === client.locales.other.unavailable ? client.locales.other.unavailable : `<${domain}${id}>` + ) + }) + .catch((e) => console.log(e)); + await client.db.set( + `tickets_${interaction.channel?.id}.transcriptURL`, + domain === client.locales.other.unavailable ? client.locales.other.unavailable : `${domain}${id}` + ); + const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); + + const row = new Discord.ActionRowBuilder().addComponents( + new Discord.ButtonBuilder().setCustomId("deleteTicket").setLabel(client.locales.other.deleteTicketButtonMSG).setStyle(Discord.ButtonStyle.Danger) + ); + const lEmbed = client.locales.embeds; + interaction.channel?.send({ + embeds: [ + JSON.parse( + JSON.stringify(lEmbed.ticketClosed) + .replace("TICKETCOUNT", ticket.id) + .replace("REASON", ticket.closeReason.replace(/[\n\r]/g, "\\n")) + .replace("CLOSERNAME", interaction.user.tag) + ) + ], + components: [row] + }) + .catch((e) => console.log(e)); + + const tiketClosedDMEmbed = new Discord.EmbedBuilder() + .setColor(lEmbed.ticketClosedDM.color ? lEmbed.ticketClosedDM.color : client.config.mainColor) + .setDescription( + client.locales.embeds.ticketClosedDM.description + .replace("TICKETCOUNT", ticket.id) + .replace("TRANSCRIPTURL", `[\`${domain}${id}\`](${domain}${id})`) + .replace("REASON", ticket.closeReason) + .replace("CLOSERNAME", interaction.user.tag) + ) + + /* + Copyright 2023 Sayrix (github.com/Sayrix) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + .setFooter({ + // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) + text: "ticket.pm" + lEmbed.ticketClosedDM.footer.text.replace("ticket.pm", ""), // Please respect the LICENSE :D + // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) + iconUrl: lEmbed.ticketClosedDM.footer.iconUrl + }); + + /* + Copyright 2023 Sayrix (github.com/Sayrix) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + client.users.fetch(creator).then((user) => { + user + .send({ + embeds: [tiketClosedDMEmbed] + }) + .catch((e) => console.log(e)); + }); + } + + if (!client.config.createTranscript) { + close(""); + return; + } + + async function fetchAll() { + let collArray: Collection>[] = []; + let lastID = (interaction.channel as TextChannel | null)?.lastMessageId; + // eslint-disable-next-line no-constant-condition + while (true) { + // using if statement for this check causes a TypeScript bug. Hard to reproduce; thus, bug report won't be accepted. + if(!lastID) break; + const fetched = await interaction.channel?.messages.fetch({ limit: 100, before: lastID }); + if (fetched?.size === 0) { + break; + } + if(fetched) + collArray.push(fetched); + lastID = fetched?.last()?.id; + if (fetched?.size !== 100) { + break; + } + } + const messages = collArray[0].concat(...collArray.slice(1)); + return messages; + } + + const messages = await fetchAll(); + const premiumKey = ""; + + const messagesJSON = await generateMessages(messages, premiumKey, "https://m.ticket.pm"); + zlib.gzip(JSON.stringify(messagesJSON), async (err, compressed) => { + if (err) { + console.error(err); + } else { + const ts = await axios + .post(`${domain}upload?key=${premiumKey}`, JSON.stringify(compressed), { + headers: { + "Content-Type": "application/json" + } + }) + .catch(console.error); + close(ts?.data); + } + }); +} + +/* +Copyright 2023 Sayrix (github.com/Sayrix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ diff --git a/src/utils/close_askReason.js b/src/utils/close_askReason.ts similarity index 50% rename from src/utils/close_askReason.js rename to src/utils/close_askReason.ts index 8408901e..782548d0 100644 --- a/src/utils/close_askReason.js +++ b/src/utils/close_askReason.ts @@ -1,5 +1,3 @@ -const Discord = require("discord.js"); - /* Copyright 2023 Sayrix (github.com/Sayrix) @@ -16,33 +14,34 @@ See the License for the specific language governing permissions and limitations under the License. */ -module.exports = { - async closeAskReason(interaction, client) { - if ( - client.config.whoCanCloseTicket === "STAFFONLY" && - !interaction.member.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id)) - ) - return interaction - .reply({ - content: client.locales.ticketOnlyClosableByStaff, - ephemeral: true, - }) - .catch((e) => console.log(e)); - - const modal = new Discord.ModalBuilder().setCustomId("askReasonClose").setTitle(client.locales.modals.reasonTicketClose.title); - - const input = new Discord.TextInputBuilder() - .setCustomId("reason") - .setLabel(client.locales.modals.reasonTicketClose.label) - .setStyle(Discord.TextInputStyle.Paragraph) - .setPlaceholder(client.locales.modals.reasonTicketClose.placeholder) - .setMaxLength(256); - - const firstActionRow = new Discord.ActionRowBuilder().addComponents(input); - modal.addComponents(firstActionRow); - await interaction.showModal(modal).catch((e) => console.log(e)); - }, -}; +import { ButtonInteraction, GuildMember } from "discord.js"; +import { DiscordClient } from "../Types"; + +export const closeAskReason = async(interaction: ButtonInteraction, client: DiscordClient) => { + if ( + client.config.whoCanCloseTicket === "STAFFONLY" && + !(interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id)) + ) + return interaction + .reply({ + content: client.locales.ticketOnlyClosableByStaff, + ephemeral: true, + }) + .catch((e) => console.log(e)); + + const modal = new Discord.ModalBuilder().setCustomId("askReasonClose").setTitle(client.locales.modals.reasonTicketClose.title); + + const input = new Discord.TextInputBuilder() + .setCustomId("reason") + .setLabel(client.locales.modals.reasonTicketClose.label) + .setStyle(Discord.TextInputStyle.Paragraph) + .setPlaceholder(client.locales.modals.reasonTicketClose.placeholder) + .setMaxLength(256); + + const firstActionRow = new Discord.ActionRowBuilder().addComponents(input); + modal.addComponents(firstActionRow); + await interaction.showModal(modal).catch((e) => console.log(e)); +} /* Copyright 2023 Sayrix (github.com/Sayrix) diff --git a/src/utils/createTicket.js b/src/utils/createTicket.js deleted file mode 100644 index 2efd41e4..00000000 --- a/src/utils/createTicket.js +++ /dev/null @@ -1,253 +0,0 @@ -const Discord = require("discord.js"); - -/* -Copyright 2023 Sayrix (github.com/Sayrix) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -module.exports = { - /** - * @param {Discord.Interaction} interaction - * @param {Discord.Client} client - * @param {Object} ticketType - * @param {Object|string} reasons - */ - async createTicket(interaction, client, ticketType, reasons) { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async function (resolve, reject) { - await interaction.deferReply({ ephemeral: true }).catch((e) => console.log(e)); - - let reason = []; - let allReasons; - if (typeof reasons === "object") { - reasons.forEach(async (r) => { - reason.push(r.value); - }); - - allReasons = reason.map((r, i) => `Question ${i + 1}: ${r}`).join(", "); - } - let ticketName = new String(); - - if (ticketType.ticketNameOption) { - ticketName = ticketType.ticketNameOption - .replace("USERNAME", interaction.user.username) - .replace("USERID", interaction.user.id) - .replace("TICKETCOUNT", (await client.db.get("temp.ticketCount")) || 0); - } else { - ticketName = client.config.ticketNameOption - .replace("USERNAME", interaction.user.username) - .replace("USERID", interaction.user.id) - .replace("TICKETCOUNT", (await client.db.get("temp.ticketCount")) || 0); - } - - const channel = await client.guilds.cache.get(client.config.guildId).channels.create({ - name: ticketName, - parent: ticketType.categoryId, - permissionOverwrites: [ - { - id: interaction.guild.roles.everyone, - deny: [Discord.PermissionFlagsBits.ViewChannel], - }, - ], - }); - - if (!channel) return reject("Couldn't create the ticket channel."); - - client.log( - "ticketCreate", - { - user: { - tag: interaction.user.tag, - id: interaction.user.id, - avatarURL: interaction.user.displayAvatarURL(), - }, - reason: allReasons, - ticketChannelId: channel.id, - }, - client - ); - - await client.db.add("temp.ticketCount", 1); - const ticketId = await client.db.get("temp.ticketCount"); - await client.db.set(`tickets_${channel.id}`, { - id: ticketId - 1, - category: ticketType, - reason: allReasons, - creator: interaction.user.id, - invited: [], - createdAt: Date.now(), - claimed: false, - claimedBy: null, - claimedAt: null, - closed: false, - closedBy: null, - closedAt: null, - }); - - channel.permissionOverwrites - .edit(interaction.user, { - SendMessages: true, - AddReactions: true, - ReadMessageHistory: true, - AttachFiles: true, - ViewChannel: true, - }) - .catch((e) => console.log(e)); - - if (client.config.rolesWhoHaveAccessToTheTickets.length > 0) { - client.config.rolesWhoHaveAccessToTheTickets.forEach(async (role) => { - channel.permissionOverwrites - .edit(role, { - SendMessages: true, - AddReactions: true, - ReadMessageHistory: true, - AttachFiles: true, - ViewChannel: true, - }) - .catch((e) => console.log(e)); - }); - } - - const ticketOpenedEmbed = new Discord.EmbedBuilder() - .setColor(ticketType.color ? ticketType.color : client.config.mainColor) - .setTitle(client.embeds.ticketOpened.title.replace("CATEGORYNAME", ticketType.name)) - .setDescription( - ticketType.customDescription - ? ticketType.customDescription - .replace("CATEGORYNAME", ticketType.name) - .replace("USERNAME", interaction.user.username) - .replace("USERID", interaction.user.id) - .replace("TICKETCOUNT", (await client.db.get("temp.ticketCount")) || 0) - .replace("REASON1", reason[0]) - .replace("REASON2", reason[1]) - .replace("REASON3", reason[2]) - .replace("REASON4", reason[3]) - .replace("REASON5", reason[4]) - .replace("REASON6", reason[5]) - .replace("REASON7", reason[6]) - .replace("REASON8", reason[7]) - .replace("REASON9", reason[8]) - : client.embeds.ticketOpened.description - .replace("CATEGORYNAME", ticketType.name) - .replace("USERNAME", interaction.user.username) - .replace("USERID", interaction.user.id) - .replace("TICKETCOUNT", (await client.db.get("temp.ticketCount")) || 0) - .replace("REASON1", reason[0]) - .replace("REASON2", reason[1]) - .replace("REASON3", reason[2]) - .replace("REASON4", reason[3]) - .replace("REASON5", reason[4]) - .replace("REASON6", reason[5]) - .replace("REASON7", reason[6]) - .replace("REASON8", reason[7]) - .replace("REASON9", reason[8]) - ) - /* - Copyright 2023 Sayrix (github.com/Sayrix) - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - .setFooter({ - // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) - text: "ticket.pm" + client.embeds.ticketOpened.footer.text.replace("ticket.pm", ""), // Please respect the LICENSE :D - // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) - iconUrl: client.embeds.ticketOpened.footer.iconUrl, - }); - /* - Copyright 2023 Sayrix (github.com/Sayrix) - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - const row = new Discord.ActionRowBuilder(); - - if (client.config.closeButton) { - if (client.config.askReasonWhenClosing) { - row.addComponents( - new Discord.ButtonBuilder() - .setCustomId("close_askReason") - .setLabel(client.locales.buttons.close.label) - .setEmoji(client.locales.buttons.close.emoji) - .setStyle(Discord.ButtonStyle.Danger) - ); - } else { - row.addComponents( - new Discord.ButtonBuilder() - .setCustomId("close") - .setLabel(client.locales.buttons.close.label) - .setEmoji(client.locales.buttons.close.emoji) - .setStyle(Discord.ButtonStyle.Danger) - ); - } - } - - if (client.config.claimButton) { - row.addComponents( - new Discord.ButtonBuilder() - .setCustomId("claim") - .setLabel(client.locales.buttons.claim.label) - .setEmoji(client.locales.buttons.claim.emoji) - .setStyle(Discord.ButtonStyle.Primary) - ); - } - - const body = { - embeds: [ticketOpenedEmbed], - content: `<@${interaction.user.id}> ${ - client.config.pingRoleWhenOpened ? client.config.roleToPingWhenOpenedId.map((x) => `<@&${x}>`).join(", ") : "" - }`, - }; - - if (row.components.length > 0) body.components = [row]; - - channel - .send(body) - .then((msg) => { - client.db.set(`tickets_${channel.id}.messageId`, msg.id); - msg.pin().then(() => { - msg.channel.bulkDelete(1); - }); - interaction - .editReply({ - content: client.locales.ticketOpenedMessage.replace("TICKETCHANNEL", `<#${channel.id}>`), - components: [], - ephemeral: true, - }) - .catch((e) => console.log(e)); - - resolve(true); - }) - .catch((e) => console.log(e)); - }); - }, -}; diff --git a/src/utils/createTicket.ts b/src/utils/createTicket.ts new file mode 100644 index 00000000..a477b950 --- /dev/null +++ b/src/utils/createTicket.ts @@ -0,0 +1,251 @@ +import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, Collection, EmbedBuilder, ModalSubmitInteraction, TextInputComponent } from "discord.js"; +import { DiscordClient } from "../Types"; +import {TicketType} from '../Types'; +import { log } from "./logs"; + +/* +Copyright 2023 Sayrix (github.com/Sayrix) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/** + * @param {Discord.Interaction} interaction + * @param {Discord.Client} client + * @param {Object} ticketType + * @param {Object|string} reasons + */ +export const createTicket = async (interaction: ModalSubmitInteraction, client: DiscordClient, ticketType: TicketType, reasons?: Collection) => { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async function (resolve, reject) { + await interaction.deferReply({ ephemeral: true }).catch((e) => console.log(e)); + + let reason: string[] = []; + let allReasons: string = ""; + + if (typeof reasons === "object") { + reasons.forEach(async (r) => { + reason.push(r.value); + }); + allReasons = reason.map((r, i) => `Question ${i + 1}: ${r}`).join(", "); + } + + let ticketName = ""; + + if (ticketType.ticketNameOption) { + ticketName = ticketType.ticketNameOption + .replace("USERNAME", interaction.user.username) + .replace("USERID", interaction.user.id) + .replace("TICKETCOUNT", (await client.db.get("temp.ticketCount")) ?? "0"); + } else { + ticketName = client.config.ticketNameOption + .replace("USERNAME", interaction.user.username) + .replace("USERID", interaction.user.id) + .replace("TICKETCOUNT", (await client.db.get("temp.ticketCount")) ?? "0"); + } + + const channel = await client.guilds.cache.get(client.config.guildId)?.channels.create({ + name: ticketName, + parent: ticketType.categoryId, + permissionOverwrites: [ + { + id: interaction.guild!.roles.everyone, + deny: [Discord.PermissionFlagsBits.ViewChannel], + }, + ], + }); + + if (!channel) return reject("Couldn't create the ticket channel."); + log( + { + LogType: "ticketCreate", + user: interaction.user, + reason: allReasons, + ticketChannelId: channel.id + }, + client + ); + + await client.db.add("temp.ticketCount", 1); + const ticketId = await client.db.get("temp.ticketCount"); + await client.db.set(`tickets_${channel.id}`, { + id: ticketId - 1, + category: ticketType, + reason: allReasons, + creator: interaction.user.id, + invited: [], + createdAt: Date.now(), + claimed: false, + claimedBy: null, + claimedAt: null, + closed: false, + closedBy: null, + closedAt: null, + }); + + channel.permissionOverwrites + .edit(interaction.user, { + SendMessages: true, + AddReactions: true, + ReadMessageHistory: true, + AttachFiles: true, + ViewChannel: true, + }) + .catch((e) => console.log(e)); + + if (client.config.rolesWhoHaveAccessToTheTickets.length > 0) { + client.config.rolesWhoHaveAccessToTheTickets.forEach(async (role) => { + channel.permissionOverwrites + .edit(role, { + SendMessages: true, + AddReactions: true, + ReadMessageHistory: true, + AttachFiles: true, + ViewChannel: true, + }) + .catch((e) => console.log(e)); + }); + } + const lEmbeds = client.locales.embeds + const ticketOpenedEmbed = new EmbedBuilder() + .setColor(`#${ticketType.color ? ticketType.color : client.config.mainColor}`) + .setTitle(lEmbeds.ticketOpened.title.replace("CATEGORYNAME", ticketType.name)) + .setDescription( + ticketType.customDescription + ? ticketType.customDescription + .replace("CATEGORYNAME", ticketType.name) + .replace("USERNAME", interaction.user.username) + .replace("USERID", interaction.user.id) + .replace("TICKETCOUNT", (await client.db.get("temp.ticketCount")) || "0") + .replace("REASON1", reason[0]) + .replace("REASON2", reason[1]) + .replace("REASON3", reason[2]) + .replace("REASON4", reason[3]) + .replace("REASON5", reason[4]) + .replace("REASON6", reason[5]) + .replace("REASON7", reason[6]) + .replace("REASON8", reason[7]) + .replace("REASON9", reason[8]) + : lEmbeds.ticketOpened.description + .replace("CATEGORYNAME", ticketType.name) + .replace("USERNAME", interaction.user.username) + .replace("USERID", interaction.user.id) + .replace("TICKETCOUNT", (await client.db.get("temp.ticketCount")) || "0") + .replace("REASON1", reason[0]) + .replace("REASON2", reason[1]) + .replace("REASON3", reason[2]) + .replace("REASON4", reason[3]) + .replace("REASON5", reason[4]) + .replace("REASON6", reason[5]) + .replace("REASON7", reason[6]) + .replace("REASON8", reason[7]) + .replace("REASON9", reason[8]) + ) + /* + Copyright 2023 Sayrix (github.com/Sayrix) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + .setFooter({ + // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) + text: "ticket.pm" + lEmbeds.ticketOpened.footer.text.replace("ticket.pm", ""), // Please respect the LICENSE :D + // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) + iconURL: lEmbeds.ticketOpened.footer.iconUrl + }); + /* + Copyright 2023 Sayrix (github.com/Sayrix) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + const row = new ActionRowBuilder(); + + if (client.config.closeButton) { + if (client.config.askReasonWhenClosing) { + row.addComponents( + new ButtonBuilder() + .setCustomId("close_askReason") + .setLabel(client.locales.buttons.close.label) + .setEmoji(client.locales.buttons.close.emoji) + .setStyle(Discord.ButtonStyle.Danger) + ); + } else { + row.addComponents( + new ButtonBuilder() + .setCustomId("close") + .setLabel(client.locales.buttons.close.label) + .setEmoji(client.locales.buttons.close.emoji) + .setStyle(Discord.ButtonStyle.Danger) + ); + } + } + + if (client.config.claimButton) { + row.addComponents( + new ButtonBuilder() + .setCustomId("claim") + .setLabel(client.locales.buttons.claim.label) + .setEmoji(client.locales.buttons.claim.emoji) + .setStyle(Discord.ButtonStyle.Primary) + ); + } + + const body = { + embeds: [ticketOpenedEmbed], + content: `<@${interaction.user.id}> ${ + client.config.pingRoleWhenOpened ? client.config.roleToPingWhenOpenedId.map((x) => `<@&${x}>`).join(", ") : "" + }`, + components: [] as ActionRowBuilder[], + }; + + if (row.components.length > 0) body.components = [row]; + + channel + .send(body) + .then((msg) => { + client.db.set(`tickets_${channel.id}.messageId`, msg.id); + msg.pin().then(() => { + msg.channel.bulkDelete(1); + }); + interaction + .editReply({ + content: client.locales.ticketOpenedMessage.replace("TICKETCHANNEL", `<#${channel.id}>`), + components: [], + + }) + .catch((e) => console.log(e)); + + resolve(true); + }) + .catch((e) => console.log(e)); + }); +} diff --git a/src/utils/delete.js b/src/utils/delete.ts similarity index 62% rename from src/utils/delete.js rename to src/utils/delete.ts index 65943dac..10e69fbf 100644 --- a/src/utils/delete.js +++ b/src/utils/delete.ts @@ -14,30 +14,26 @@ See the License for the specific language governing permissions and limitations under the License. */ -module.exports = { - async deleteTicket(interaction, client) { - const ticket = await client.db.get(`tickets_${interaction.channel.id}`); - if (!ticket) return interaction.reply({ content: "Ticket not found", ephemeral: true }).catch((e) => console.log(e)); - - client.log( - "ticketDelete", - { - user: { - tag: interaction.user.tag, - id: interaction.user.id, - avatarURL: interaction.user.displayAvatarURL(), - }, - ticketId: ticket.id, - ticketCreatedAt: ticket.createdAt, - transcriptURL: ticket.transcriptURL, - }, - client - ); - - await interaction.deferUpdate(); - interaction.channel.delete().catch((e) => console.log(e)); - }, -}; +import { ButtonInteraction } from "discord.js"; +import { DiscordClient } from "../Types"; +import { log } from "./logs"; + +export const deleteTicket = async (interaction: ButtonInteraction, client: DiscordClient) => { + const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); + if (!ticket) return interaction.reply({ content: "Ticket not found", ephemeral: true }).catch((e) => console.log(e)) + log( + { + LogType: "ticketDelete", + user: interaction.user, + ticketId: ticket.id, + ticketCreatedAt: ticket.createdAt, + transcriptURL: ticket.transcriptURL, + }, + client + ); + await interaction.deferUpdate(); + interaction.channel?.delete().catch((e) => console.log(e)); +} /* Copyright 2023 Sayrix (github.com/Sayrix) diff --git a/src/utils/logs.ts b/src/utils/logs.ts index 3521b059..a32f87bb 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -21,7 +21,7 @@ type log = { LogType: "ticketCreate" user: User ticketChannelId?: string; - reason: string; + reason?: string; } | { LogType: "ticketClaim" | "ticketClose" user: User @@ -41,7 +41,9 @@ type log = { } | { LogType: "userAdded" | "userRemoved" user: User; - target: User; + target: { + id?: string + }; ticketChannelId?: string; reason?: string; ticketId?: string; diff --git a/src/utils/test.ts b/src/utils/test.ts new file mode 100644 index 00000000..239d27a0 --- /dev/null +++ b/src/utils/test.ts @@ -0,0 +1,18 @@ +async function waiter(a: string) { + return { + kc: "Aa" + }; +} + +async function waiters(a?: string) { + return a; +} + +async function test() { + let a = await waiters(); + if(!a) return; + while(true) { + const k = await waiter(a); + a = k.kc; + } +} \ No newline at end of file From 865b9b5e841de4443abb75b867371f56453c8c53 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 20:59:21 -0400 Subject: [PATCH 12/52] Fixed eslint and more typescript stuff --- .eslintrc.js | 25 ---- .eslintrc.json | 27 +++++ src/commands/add.ts | 2 +- src/commands/claim.ts | 4 +- src/commands/close.ts | 6 +- src/commands/remove.ts | 2 +- src/commands/rename.ts | 6 +- src/utils/claim.ts | 20 ++-- src/utils/close.ts | 46 ++++---- src/utils/close_askReason.ts | 6 +- src/utils/createTicket.ts | 12 +- src/utils/delete.ts | 4 +- src/utils/logs.ts | 222 +++++++++++++++++------------------ src/utils/test.ts | 18 --- 14 files changed, 191 insertions(+), 209 deletions(-) delete mode 100644 .eslintrc.js create mode 100644 .eslintrc.json delete mode 100644 src/utils/test.ts diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 2df9655b..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,25 +0,0 @@ -module.exports = { - env: { - browser: true, - commonjs: true, - es2021: true, - }, - overrides: [], - extends: ["eslint:recommended", "plugin:node/recommended", "prettier", "plugin:@typescript-eslint/recommended"], - parser: "@typescript-eslint/parser", - plugins: ["@typescript-eslint"], - parserOptions: { - ecmaVersion: "latest", - }, - rules: { - "no-unused-vars": "warn", - "no-console": "off", - "no-undef": "warn", - "no-constant-condition": "warn", - "indent": ["error", "tab"], - "semi": ["error", "always"], - "quotes": [2, "double"], - "semi-style": ["error", "last"], - "no-process-exit": "off" - }, -}; diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..407f642a --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,27 @@ +{ + "env": { + "es2021": true, + "node": true + }, + "overrides": [], + "extends": ["eslint:recommended", "plugin:node/recommended", "prettier", "plugin:@typescript-eslint/recommended"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": ["@typescript-eslint"], + "rules": { + "no-unused-vars": "warn", + "no-console": "off", + "no-undef": "warn", + "no-constant-condition": "warn", + "indent": ["error", "tab"], + "semi": ["error", "always"], + "quotes": [2, "double"], + "semi-style": ["error", "last"], + "no-process-exit": "off", + "node/no-missing-import": "off", + "no-var-requires": "off" + } +} diff --git a/src/commands/add.ts b/src/commands/add.ts index 821dedd6..62bc3923 100644 --- a/src/commands/add.ts +++ b/src/commands/add.ts @@ -18,7 +18,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -module.exports = { +export default { data: new SlashCommandBuilder() .setName("add") .setDescription("Add someone to the ticket") diff --git a/src/commands/claim.ts b/src/commands/claim.ts index fe19f127..e83f49ec 100644 --- a/src/commands/claim.ts +++ b/src/commands/claim.ts @@ -1,5 +1,6 @@ import { CommandInteraction, SlashCommandBuilder } from "discord.js"; import { DiscordClient } from "../Types"; +import {claim} from "../utils/claim"; /* Copyright 2023 Sayrix (github.com/Sayrix) @@ -17,10 +18,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -module.exports = { +export default { data: new SlashCommandBuilder().setName("claim").setDescription("Set the ticket as claimed."), async execute(interaction: CommandInteraction, client: DiscordClient) { - const { claim } = require("../utils/claim.js"); claim(interaction, client); }, }; diff --git a/src/commands/close.ts b/src/commands/close.ts index 9bd506a1..c3b0edb6 100644 --- a/src/commands/close.ts +++ b/src/commands/close.ts @@ -1,5 +1,7 @@ import { CommandInteraction, GuildMember, SlashCommandBuilder } from "discord.js"; import { DiscordClient } from "../Types"; +import { closeAskReason } from "../utils/close_askReason"; +import {close} from "../utils/close.js"; /* Copyright 2023 Sayrix (github.com/Sayrix) @@ -17,7 +19,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -module.exports = { +export default { data: new SlashCommandBuilder().setName("close").setDescription("Close the ticket"), async execute(interaction: CommandInteraction, client: DiscordClient) { if ( @@ -32,11 +34,9 @@ module.exports = { .catch((e) => console.log(e)); if (client.config.askReasonWhenClosing) { - const { closeAskReason } = require("../utils/close_askReason.js"); closeAskReason(interaction, client); } else { await interaction.deferReply().catch((e) => console.log(e)); - const { close } = require("../utils/close.js"); close(interaction, client); } }, diff --git a/src/commands/remove.ts b/src/commands/remove.ts index 07aa9927..cc9da471 100644 --- a/src/commands/remove.ts +++ b/src/commands/remove.ts @@ -17,7 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -module.exports = { +export default{ data: new SlashCommandBuilder().setName("remove").setDescription("Remove someone from the ticket"), async execute(interaction: CommandInteraction, client: DiscordClient) { diff --git a/src/commands/rename.ts b/src/commands/rename.ts index 0a5255b9..e096f49d 100644 --- a/src/commands/rename.ts +++ b/src/commands/rename.ts @@ -1,5 +1,5 @@ -import { CommandInteraction, GuildMember, SlashCommandBuilder, TextChannel } from 'discord.js'; -import { DiscordClient } from '../Types'; +import { CommandInteraction, GuildMember, SlashCommandBuilder, TextChannel } from "discord.js"; +import { DiscordClient } from "../Types"; /* Copyright 2023 Sayrix (github.com/Sayrix) @@ -17,7 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -module.exports = { +export default { data: new SlashCommandBuilder() .setName("rename") .setDescription("Rename the ticket") diff --git a/src/utils/claim.ts b/src/utils/claim.ts index da7b8595..a1419c24 100644 --- a/src/utils/claim.ts +++ b/src/utils/claim.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { ButtonInteraction, GuildMember, TextChannel } from "discord.js"; +import { ButtonInteraction, CommandInteraction, EmbedBuilder, GuildMember, TextChannel } from "discord.js"; import { DiscordClient } from "../Types"; import { log } from "./logs"; @@ -23,7 +23,7 @@ import { log } from "./logs"; * @param {Discord.CommandInteraction} interaction * @param {Discord.Client} client */ -export const claim = async(interaction: ButtonInteraction, client: DiscordClient) => { +export const claim = async(interaction: ButtonInteraction | CommandInteraction, client: DiscordClient) => { const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); if (!ticket) return interaction.reply({ @@ -67,19 +67,21 @@ export const claim = async(interaction: ButtonInteraction, client: DiscordClient //await interaction.channel?.messages.fetch(); // Commented bc it seems useless const messageId = await client.db.get(`tickets_${interaction.channel?.id}.messageId`); const msg = interaction.channel?.messages.cache.get(messageId); - const embed = msg?.embeds[0].data; - //@ts-ignore TODO: Remove this illegal usage without breaking code - embed.description = embed?.description + `\n\n ${client.locales.other.claimedBy.replace("USER", `<@${interaction.user.id}>`)}`; + const oldEmbed = msg?.embeds[0].data; + const newEmbed = new EmbedBuilder(oldEmbed) + .setDescription(oldEmbed?.description + `\n\n ${client.locales.other.claimedBy.replace("USER", `<@${interaction.user.id}>`)}`) + new Compoenent + await msg?.edit({content: msg.content, embeds: msg.embeds, components:[]}) msg?.components[0].components.map((x) => { - //@ts-ignore TODO: Remove this illegal usage without breaking code - if (x.data.custom_id === "claim") x.data.disabled = true; + + //if (x.customId === "claim") }); + msg?.components[0].data. msg?.edit({ content: msg.content, - //@ts-ignore TODO: Remove this illegal usage without breaking code - embeds: [embed], + embeds: [newEmbed], components: msg.components, }) .catch((e) => console.log(e)); diff --git a/src/utils/close.ts b/src/utils/close.ts index 0a5b0207..e10604a8 100644 --- a/src/utils/close.ts +++ b/src/utils/close.ts @@ -1,7 +1,7 @@ import { generateMessages } from "ticket-bot-transcript-uploader"; import zlib from "zlib"; import axios from "axios"; -import { Collection, GuildMember, Message, ModalSubmitInteraction, TextBasedChannel, TextChannel, TextChannelType } from "discord.js"; +import { Collection, CommandInteraction, GuildMember, Message, ModalSubmitInteraction, TextChannel } from "discord.js"; import { DiscordClient } from "../Types"; import { log } from "./logs"; let domain = "https://ticket.pm/"; @@ -21,7 +21,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -export async function close(interaction: ModalSubmitInteraction, client: DiscordClient, reason?: string) { +export async function close(interaction: ModalSubmitInteraction | CommandInteraction, client: DiscordClient, reason?: string) { if (!client.config.createTranscript) domain = client.locales.other.unavailable; const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); @@ -105,21 +105,21 @@ export async function close(interaction: ModalSubmitInteraction, client: Discord }); msg?.edit({ - content: msg.content, - //@ts-ignore TODO: Remove this illegal usage without breaking code - embeds: [embed], - components: msg.components - }) + content: msg.content, + //@ts-ignore TODO: Remove this illegal usage without breaking code + embeds: [embed], + components: msg.components + }) .catch((e) => console.log(e)); await client.db.set(`tickets_${interaction.channel?.id}.closed`, true); interaction.channel?.send({ - content: client.locales.ticketTranscriptCreated.replace( - "TRANSCRIPTURL", - domain === client.locales.other.unavailable ? client.locales.other.unavailable : `<${domain}${id}>` - ) - }) + content: client.locales.ticketTranscriptCreated.replace( + "TRANSCRIPTURL", + domain === client.locales.other.unavailable ? client.locales.other.unavailable : `<${domain}${id}>` + ) + }) .catch((e) => console.log(e)); await client.db.set( `tickets_${interaction.channel?.id}.transcriptURL`, @@ -132,16 +132,16 @@ export async function close(interaction: ModalSubmitInteraction, client: Discord ); const lEmbed = client.locales.embeds; interaction.channel?.send({ - embeds: [ - JSON.parse( - JSON.stringify(lEmbed.ticketClosed) - .replace("TICKETCOUNT", ticket.id) - .replace("REASON", ticket.closeReason.replace(/[\n\r]/g, "\\n")) - .replace("CLOSERNAME", interaction.user.tag) - ) - ], - components: [row] - }) + embeds: [ + JSON.parse( + JSON.stringify(lEmbed.ticketClosed) + .replace("TICKETCOUNT", ticket.id) + .replace("REASON", ticket.closeReason.replace(/[\n\r]/g, "\\n")) + .replace("CLOSERNAME", interaction.user.tag) + ) + ], + components: [row] + }) .catch((e) => console.log(e)); const tiketClosedDMEmbed = new Discord.EmbedBuilder() @@ -208,7 +208,7 @@ export async function close(interaction: ModalSubmitInteraction, client: Discord } async function fetchAll() { - let collArray: Collection>[] = []; + const collArray: Collection>[] = []; let lastID = (interaction.channel as TextChannel | null)?.lastMessageId; // eslint-disable-next-line no-constant-condition while (true) { diff --git a/src/utils/close_askReason.ts b/src/utils/close_askReason.ts index 782548d0..1c968281 100644 --- a/src/utils/close_askReason.ts +++ b/src/utils/close_askReason.ts @@ -14,10 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { ButtonInteraction, GuildMember } from "discord.js"; +import { ButtonInteraction, CommandInteraction, GuildMember } from "discord.js"; import { DiscordClient } from "../Types"; -export const closeAskReason = async(interaction: ButtonInteraction, client: DiscordClient) => { +export const closeAskReason = async(interaction: CommandInteraction, client: DiscordClient) => { if ( client.config.whoCanCloseTicket === "STAFFONLY" && !(interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id)) @@ -41,7 +41,7 @@ export const closeAskReason = async(interaction: ButtonInteraction, client: Disc const firstActionRow = new Discord.ActionRowBuilder().addComponents(input); modal.addComponents(firstActionRow); await interaction.showModal(modal).catch((e) => console.log(e)); -} +}; /* Copyright 2023 Sayrix (github.com/Sayrix) diff --git a/src/utils/createTicket.ts b/src/utils/createTicket.ts index a477b950..b4e2a6f8 100644 --- a/src/utils/createTicket.ts +++ b/src/utils/createTicket.ts @@ -1,4 +1,4 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, Collection, EmbedBuilder, ModalSubmitInteraction, TextInputComponent } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, Collection, EmbedBuilder, ModalSubmitInteraction, TextInputComponent } from "discord.js"; import { DiscordClient } from "../Types"; import {TicketType} from '../Types'; import { log } from "./logs"; @@ -116,7 +116,7 @@ export const createTicket = async (interaction: ModalSubmitInteraction, client: .catch((e) => console.log(e)); }); } - const lEmbeds = client.locales.embeds + const lEmbeds = client.locales.embeds; const ticketOpenedEmbed = new EmbedBuilder() .setColor(`#${ticketType.color ? ticketType.color : client.config.mainColor}`) .setTitle(lEmbeds.ticketOpened.title.replace("CATEGORYNAME", ticketType.name)) @@ -196,7 +196,7 @@ export const createTicket = async (interaction: ModalSubmitInteraction, client: .setCustomId("close_askReason") .setLabel(client.locales.buttons.close.label) .setEmoji(client.locales.buttons.close.emoji) - .setStyle(Discord.ButtonStyle.Danger) + .setStyle(ButtonStyle.Danger) ); } else { row.addComponents( @@ -204,7 +204,7 @@ export const createTicket = async (interaction: ModalSubmitInteraction, client: .setCustomId("close") .setLabel(client.locales.buttons.close.label) .setEmoji(client.locales.buttons.close.emoji) - .setStyle(Discord.ButtonStyle.Danger) + .setStyle(ButtonStyle.Danger) ); } } @@ -215,7 +215,7 @@ export const createTicket = async (interaction: ModalSubmitInteraction, client: .setCustomId("claim") .setLabel(client.locales.buttons.claim.label) .setEmoji(client.locales.buttons.claim.emoji) - .setStyle(Discord.ButtonStyle.Primary) + .setStyle(ButtonStyle.Primary) ); } @@ -248,4 +248,4 @@ export const createTicket = async (interaction: ModalSubmitInteraction, client: }) .catch((e) => console.log(e)); }); -} +}; diff --git a/src/utils/delete.ts b/src/utils/delete.ts index 10e69fbf..090ca751 100644 --- a/src/utils/delete.ts +++ b/src/utils/delete.ts @@ -20,7 +20,7 @@ import { log } from "./logs"; export const deleteTicket = async (interaction: ButtonInteraction, client: DiscordClient) => { const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); - if (!ticket) return interaction.reply({ content: "Ticket not found", ephemeral: true }).catch((e) => console.log(e)) + if (!ticket) return interaction.reply({ content: "Ticket not found", ephemeral: true }).catch((e) => console.log(e)); log( { LogType: "ticketDelete", @@ -33,7 +33,7 @@ export const deleteTicket = async (interaction: ButtonInteraction, client: Disco ); await interaction.deferUpdate(); interaction.channel?.delete().catch((e) => console.log(e)); -} +}; /* Copyright 2023 Sayrix (github.com/Sayrix) diff --git a/src/utils/logs.ts b/src/utils/logs.ts index a32f87bb..8f7af431 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -51,126 +51,122 @@ type log = { export const log = async(logs: log, client: DiscordClient) => { - if (!client.config.logs) return; - if (!client.config.logsChannelId) return; - const channel = await client.channels - .fetch(client.config.logsChannelId) - .catch((e) => console.error("The channel to log events is not found!\n", e)); - if (!channel) return console.error("The channel to log events is not found!"); - if (!channel.isTextBased() || + if (!client.config.logs) return; + if (!client.config.logsChannelId) return; + const channel = await client.channels + .fetch(client.config.logsChannelId) + .catch((e) => console.error("The channel to log events is not found!\n", e)); + if (!channel) return console.error("The channel to log events is not found!"); + if (!channel.isTextBased() || channel.type === ChannelType.DM || channel.type === ChannelType.PrivateThread || channel.type === ChannelType.PublicThread) return console.error("Invalid Channel!"); - const webhook = (await (channel as TextChannel).fetchWebhooks()).find((wh) => wh.token) ?? + const webhook = (await (channel as TextChannel).fetchWebhooks()).find((wh) => wh.token) ?? await (channel as TextChannel).createWebhook({ name: "Ticket Bot Logs" }); - if (logs.LogType === "ticketCreate") { - const embed = new Discord.EmbedBuilder() - .setColor("#3ba55c") - .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) - .setDescription(`${logs.user.tag} (<@${logs.user.id}>) Created a ticket (<#${logs.ticketChannelId}>) with the reason: \`${logs.reason}\``); - - webhook - .send({ - username: "Ticket Created", - avatarURL: "https://i.imgur.com/M38ZmjM.png", - embeds: [embed], - }) - .catch((e) => console.log(e)); - } - - if (logs.LogType === "ticketClaim") { - const embed = new Discord.EmbedBuilder() - .setColor("#faa61a") - .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) - .setDescription( - `${logs.user.tag} (<@${logs.user.id}>) Claimed the ticket n°${logs.ticketId} (<#${logs.ticketChannelId}>) after ${client.msToHm( - new Date(Date.now() - logs.ticketCreatedAt) - )} of creation` - ); - - webhook - .send({ - username: "Ticket Claimed", - avatarURL: "https://i.imgur.com/qqEaUyR.png", - embeds: [embed], - }) - .catch((e) => console.log(e)); - } - - if (logs.LogType === "ticketClose") { - const embed = new Discord.EmbedBuilder() - .setColor("#ed4245") - .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) - .setDescription( - `${logs.user.tag} (<@${logs.user.id}>) Closed the ticket n°${logs.ticketId} (<#${logs.ticketChannelId}>) with the reason: \`${ - logs.reason - }\` after ${client.msToHm(new Date(Date.now() - logs.ticketCreatedAt))} of creation` - ); - - webhook - .send({ - username: "Ticket Closed", - avatarURL: "https://i.imgur.com/5ShDA4g.png", - embeds: [embed], - }) - .catch((e) => console.log(e)); - } - - if (logs.LogType === "ticketDelete") { - const embed = new Discord.EmbedBuilder() - .setColor("#ed4245") - .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) - .setDescription( - `${logs.user.tag} (<@${logs.user.id}>) Deleted the ticket n°${logs.ticketId} after ${client.msToHm( - new Date(Date.now() - logs.ticketCreatedAt) - )} of creation\n\nTranscript: ${logs.transcriptURL}` - ); - - webhook - .send({ - username: "Ticket Deleted", - avatarURL: "https://i.imgur.com/obTW2BS.png", - embeds: [embed], - }) - .catch((e) => console.log(e)); - } - - if (logs.LogType === "userAdded") { - const embed = new Discord.EmbedBuilder() - .setColor("#3ba55c") - .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) - .setDescription( - `${logs.user.tag} (<@${logs.user.id}>) Added <@${logs.target.id}> (${logs.target.id}) to the ticket n°${logs.ticketId} (<#${logs.ticketChannelId}>)` - ); - - webhook - .send({ - username: "User Added", - avatarURL: "https://i.imgur.com/G6QPFBV.png", - embeds: [embed], - }) - .catch((e) => console.log(e)); - } - - if (logs.LogType === "userRemoved") { - const embed = new Discord.EmbedBuilder() - .setColor("#ed4245") - .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) - .setDescription( - `${logs.user.tag} (<@${logs.user.id}>) Removed <@${logs.target.id}> (${logs.target.id}) from the ticket n°${logs.ticketId} (<#${logs.ticketChannelId}>)` - ); - - webhook - .send({ - username: "User Removed", - avatarURL: "https://i.imgur.com/eFJ8xxC.png", - embeds: [embed], - }) - .catch((e) => console.log(e)); - } + if (logs.LogType === "ticketCreate") { + const embed = new Discord.EmbedBuilder() + .setColor("#3ba55c") + .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) + .setDescription(`${logs.user.tag} (<@${logs.user.id}>) Created a ticket (<#${logs.ticketChannelId}>) with the reason: \`${logs.reason}\``); + + webhook + .send({ + username: "Ticket Created", + avatarURL: "https://i.imgur.com/M38ZmjM.png", + embeds: [embed], + }) + .catch((e) => console.log(e)); } + if (logs.LogType === "ticketClaim") { + const embed = new Discord.EmbedBuilder() + .setColor("#faa61a") + .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) + .setDescription( + `${logs.user.tag} (<@${logs.user.id}>) Claimed the ticket n°${logs.ticketId} (<#${logs.ticketChannelId}>) after ${client.msToHm( + new Date(Date.now() - logs.ticketCreatedAt) + )} of creation` + ); + webhook + .send({ + username: "Ticket Claimed", + avatarURL: "https://i.imgur.com/qqEaUyR.png", + embeds: [embed], + }) + .catch((e) => console.log(e)); + } + + if (logs.LogType === "ticketClose") { + const embed = new Discord.EmbedBuilder() + .setColor("#ed4245") + .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) + .setDescription( + `${logs.user.tag} (<@${logs.user.id}>) Closed the ticket n°${logs.ticketId} (<#${logs.ticketChannelId}>) with the reason: \`${ + logs.reason + }\` after ${client.msToHm(new Date(Date.now() - logs.ticketCreatedAt))} of creation` + ); + + webhook + .send({ + username: "Ticket Closed", + avatarURL: "https://i.imgur.com/5ShDA4g.png", + embeds: [embed], + }) + .catch((e) => console.log(e)); + } + + if (logs.LogType === "ticketDelete") { + const embed = new Discord.EmbedBuilder() + .setColor("#ed4245") + .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) + .setDescription( + `${logs.user.tag} (<@${logs.user.id}>) Deleted the ticket n°${logs.ticketId} after ${client.msToHm( + new Date(Date.now() - logs.ticketCreatedAt) + )} of creation\n\nTranscript: ${logs.transcriptURL}` + ); + + webhook + .send({ + username: "Ticket Deleted", + avatarURL: "https://i.imgur.com/obTW2BS.png", + embeds: [embed], + }) + .catch((e) => console.log(e)); + } + + if (logs.LogType === "userAdded") { + const embed = new Discord.EmbedBuilder() + .setColor("#3ba55c") + .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) + .setDescription( + `${logs.user.tag} (<@${logs.user.id}>) Added <@${logs.target.id}> (${logs.target.id}) to the ticket n°${logs.ticketId} (<#${logs.ticketChannelId}>)` + ); + + webhook + .send({ + username: "User Added", + avatarURL: "https://i.imgur.com/G6QPFBV.png", + embeds: [embed], + }) + .catch((e) => console.log(e)); + } + if (logs.LogType === "userRemoved") { + const embed = new Discord.EmbedBuilder() + .setColor("#ed4245") + .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) + .setDescription( + `${logs.user.tag} (<@${logs.user.id}>) Removed <@${logs.target.id}> (${logs.target.id}) from the ticket n°${logs.ticketId} (<#${logs.ticketChannelId}>)` + ); + webhook + .send({ + username: "User Removed", + avatarURL: "https://i.imgur.com/eFJ8xxC.png", + embeds: [embed], + }) + .catch((e) => console.log(e)); + } +}; /* Copyright 2023 Sayrix (github.com/Sayrix) diff --git a/src/utils/test.ts b/src/utils/test.ts deleted file mode 100644 index 239d27a0..00000000 --- a/src/utils/test.ts +++ /dev/null @@ -1,18 +0,0 @@ -async function waiter(a: string) { - return { - kc: "Aa" - }; -} - -async function waiters(a?: string) { - return a; -} - -async function test() { - let a = await waiters(); - if(!a) return; - while(true) { - const k = await waiter(a); - a = k.kc; - } -} \ No newline at end of file From da54466847809550c9084057e5ae74b0aa683d66 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 23:04:51 -0400 Subject: [PATCH 13/52] potentially fixed close.ts bugs due to invalid way of disabling buttons --- src/utils/close.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/utils/close.ts b/src/utils/close.ts index e10604a8..e1100e67 100644 --- a/src/utils/close.ts +++ b/src/utils/close.ts @@ -1,7 +1,7 @@ import { generateMessages } from "ticket-bot-transcript-uploader"; import zlib from "zlib"; import axios from "axios"; -import { Collection, CommandInteraction, GuildMember, Message, ModalSubmitInteraction, TextChannel } from "discord.js"; +import { ActionRow, ActionRowBuilder, ButtonBuilder, ButtonComponent, Collection, CommandInteraction, ComponentBuilder, ComponentType, EmbedBuilder, GuildMember, Message, ModalSubmitInteraction, StringSelectMenuBuilder, TextChannel } from "discord.js"; import { DiscordClient } from "../Types"; import { log } from "./logs"; let domain = "https://ticket.pm/"; @@ -95,20 +95,21 @@ export async function close(interaction: ModalSubmitInteraction | CommandInterac const messageId = await client.db.get(`tickets_${interaction.channel?.id}.messageId`); const msg = interaction.channel?.messages.cache.get(messageId); - const embed = msg?.embeds[0].data; + const embed = new EmbedBuilder(msg?.embeds[0].data); + const rowAction = new ActionRowBuilder(); msg?.components[0]?.components?.map((x) => { - //@ts-ignore TODO: Remove this illegal usage without breaking code - if (x.data.custom_id === "close") x.data.disabled = true; - //@ts-ignore TODO: Remove this illegal usage without breaking code - if (x.data.custom_id === "close_askReason") x.data.disabled = true; + if(x.type !== ComponentType.Button) return; + const builder = new ButtonBuilder(x.data); + if (x.customId === "close") builder.setDisabled(true); + if (x.customId === "close_askReason") builder.setDisabled(true); + rowAction.addComponents(builder); }); msg?.edit({ content: msg.content, - //@ts-ignore TODO: Remove this illegal usage without breaking code embeds: [embed], - components: msg.components + components: [rowAction] }) .catch((e) => console.log(e)); From caf305f0048d1f4dea6c129e4e195787ce6a0a90 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 23:05:58 -0400 Subject: [PATCH 14/52] fixed other close.ts usage --- src/utils/close.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/utils/close.ts b/src/utils/close.ts index e1100e67..0487098f 100644 --- a/src/utils/close.ts +++ b/src/utils/close.ts @@ -1,7 +1,7 @@ import { generateMessages } from "ticket-bot-transcript-uploader"; import zlib from "zlib"; import axios from "axios"; -import { ActionRow, ActionRowBuilder, ButtonBuilder, ButtonComponent, Collection, CommandInteraction, ComponentBuilder, ComponentType, EmbedBuilder, GuildMember, Message, ModalSubmitInteraction, StringSelectMenuBuilder, TextChannel } from "discord.js"; +import { ActionRow, ActionRowBuilder, ButtonBuilder, ButtonComponent, ButtonStyle, Collection, CommandInteraction, ComponentBuilder, ComponentType, EmbedBuilder, GuildMember, Message, ModalSubmitInteraction, StringSelectMenuBuilder, TextChannel } from "discord.js"; import { DiscordClient } from "../Types"; import { log } from "./logs"; let domain = "https://ticket.pm/"; @@ -128,8 +128,8 @@ export async function close(interaction: ModalSubmitInteraction | CommandInterac ); const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); - const row = new Discord.ActionRowBuilder().addComponents( - new Discord.ButtonBuilder().setCustomId("deleteTicket").setLabel(client.locales.other.deleteTicketButtonMSG).setStyle(Discord.ButtonStyle.Danger) + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder().setCustomId("deleteTicket").setLabel(client.locales.other.deleteTicketButtonMSG).setStyle(ButtonStyle.Danger) ); const lEmbed = client.locales.embeds; interaction.channel?.send({ @@ -145,8 +145,8 @@ export async function close(interaction: ModalSubmitInteraction | CommandInterac }) .catch((e) => console.log(e)); - const tiketClosedDMEmbed = new Discord.EmbedBuilder() - .setColor(lEmbed.ticketClosedDM.color ? lEmbed.ticketClosedDM.color : client.config.mainColor) + const tiketClosedDMEmbed = new EmbedBuilder() + .setColor(`#${lEmbed.ticketClosedDM.color ? lEmbed.ticketClosedDM.color : client.config.mainColor}`) .setDescription( client.locales.embeds.ticketClosedDM.description .replace("TICKETCOUNT", ticket.id) @@ -175,7 +175,7 @@ export async function close(interaction: ModalSubmitInteraction | CommandInterac // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) text: "ticket.pm" + lEmbed.ticketClosedDM.footer.text.replace("ticket.pm", ""), // Please respect the LICENSE :D // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) - iconUrl: lEmbed.ticketClosedDM.footer.iconUrl + iconURL: lEmbed.ticketClosedDM.footer.iconUrl }); /* From 1bf9002a32365dfbd7106872852fa579721b5a05 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 23:12:28 -0400 Subject: [PATCH 15/52] Fixed claim.ts usage --- src/utils/claim.ts | 80 ++++++++++++++++++------------------ src/utils/close_askReason.ts | 10 ++--- 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/src/utils/claim.ts b/src/utils/claim.ts index a1419c24..6163575d 100644 --- a/src/utils/claim.ts +++ b/src/utils/claim.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { ButtonInteraction, CommandInteraction, EmbedBuilder, GuildMember, TextChannel } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, CommandInteraction, EmbedBuilder, GuildMember, TextChannel } from "discord.js"; import { DiscordClient } from "../Types"; import { log } from "./logs"; @@ -24,16 +24,16 @@ import { log } from "./logs"; * @param {Discord.Client} client */ export const claim = async(interaction: ButtonInteraction | CommandInteraction, client: DiscordClient) => { - const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); - if (!ticket) + const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); + if (!ticket) return interaction.reply({ content: "Ticket not found", ephemeral: true, }); - const canClaim = (interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id)); + const canClaim = (interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id)); - if (!canClaim) + if (!canClaim) return interaction .reply({ content: client.locales.ticketOnlyClaimableByStaff, @@ -41,7 +41,7 @@ export const claim = async(interaction: ButtonInteraction | CommandInteraction, }) .catch((e) => console.log(e)); - if (ticket.claimed) + if (ticket.claimed) return interaction .reply({ content: client.locales.ticketAlreadyClaimed, @@ -49,54 +49,52 @@ export const claim = async(interaction: ButtonInteraction | CommandInteraction, }) .catch((e) => console.log(e)); - log( + log( { - LogType: "ticketClaim", - user: interaction.user, - ticketId: ticket.id, - ticketChannelId: interaction.channel?.id, - ticketCreatedAt: ticket.createdAt, + LogType: "ticketClaim", + user: interaction.user, + ticketId: ticket.id, + ticketChannelId: interaction.channel?.id, + ticketCreatedAt: ticket.createdAt, }, client - ); - - await client.db.set(`tickets_${interaction.channel?.id}.claimed`, true); - await client.db.set(`tickets_${interaction.channel?.id}.claimedBy`, interaction.user.id); - await client.db.set(`tickets_${interaction.channel?.id}.claimedAt`, Date.now()); - - //await interaction.channel?.messages.fetch(); // Commented bc it seems useless - const messageId = await client.db.get(`tickets_${interaction.channel?.id}.messageId`); - const msg = interaction.channel?.messages.cache.get(messageId); - const oldEmbed = msg?.embeds[0].data; - const newEmbed = new EmbedBuilder(oldEmbed) - .setDescription(oldEmbed?.description + `\n\n ${client.locales.other.claimedBy.replace("USER", `<@${interaction.user.id}>`)}`) - - new Compoenent - await msg?.edit({content: msg.content, embeds: msg.embeds, components:[]}) - msg?.components[0].components.map((x) => { - - //if (x.customId === "claim") - }); - msg?.components[0].data. - - msg?.edit({ + ); + + await client.db.set(`tickets_${interaction.channel?.id}.claimed`, true); + await client.db.set(`tickets_${interaction.channel?.id}.claimedBy`, interaction.user.id); + await client.db.set(`tickets_${interaction.channel?.id}.claimedAt`, Date.now()); + + //await interaction.channel?.messages.fetch(); // Commented bc it seems useless + const messageId = await client.db.get(`tickets_${interaction.channel?.id}.messageId`); + const msg = interaction.channel?.messages.cache.get(messageId); + const oldEmbed = msg?.embeds[0].data; + const newEmbed = new EmbedBuilder(oldEmbed) + .setDescription(oldEmbed?.description + `\n\n ${client.locales.other.claimedBy.replace("USER", `<@${interaction.user.id}>`)}`); + + const row = new ActionRowBuilder(); + msg?.components[0].components.map((x) => { + const btnBuilder = new ButtonBuilder(x.data); + if (x.customId === "claim") btnBuilder.setDisabled(true); + row.addComponents(btnBuilder); + }); + + msg?.edit({ content: msg.content, embeds: [newEmbed], - components: msg.components, - }) - .catch((e) => console.log(e)); + components: [row], + }).catch((e) => console.log(e)); - interaction + interaction .reply({ content: client.locales.ticketClaimedMessage.replace("USER", `<@${interaction.user.id}>`), ephemeral: false, }) .catch((e) => console.log(e)); - if (client.config.ticketNamePrefixWhenClaimed) { + if (client.config.ticketNamePrefixWhenClaimed) { (interaction.channel as TextChannel | null)?.setName(`${client.config.ticketNamePrefixWhenClaimed}${(interaction.channel as TextChannel | null)?.name}`).catch((e) => console.log(e)); - } -} + } +}; /* Copyright 2023 Sayrix (github.com/Sayrix) diff --git a/src/utils/close_askReason.ts b/src/utils/close_askReason.ts index 1c968281..3eaa9dd0 100644 --- a/src/utils/close_askReason.ts +++ b/src/utils/close_askReason.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { ButtonInteraction, CommandInteraction, GuildMember } from "discord.js"; +import { ActionRowBuilder, CommandInteraction, GuildMember, ModalBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; import { DiscordClient } from "../Types"; export const closeAskReason = async(interaction: CommandInteraction, client: DiscordClient) => { @@ -29,16 +29,16 @@ export const closeAskReason = async(interaction: CommandInteraction, client: Dis }) .catch((e) => console.log(e)); - const modal = new Discord.ModalBuilder().setCustomId("askReasonClose").setTitle(client.locales.modals.reasonTicketClose.title); + const modal = new ModalBuilder().setCustomId("askReasonClose").setTitle(client.locales.modals.reasonTicketClose.title); - const input = new Discord.TextInputBuilder() + const input = new TextInputBuilder() .setCustomId("reason") .setLabel(client.locales.modals.reasonTicketClose.label) - .setStyle(Discord.TextInputStyle.Paragraph) + .setStyle(TextInputStyle.Paragraph) .setPlaceholder(client.locales.modals.reasonTicketClose.placeholder) .setMaxLength(256); - const firstActionRow = new Discord.ActionRowBuilder().addComponents(input); + const firstActionRow = new ActionRowBuilder().addComponents(input); modal.addComponents(firstActionRow); await interaction.showModal(modal).catch((e) => console.log(e)); }; From 1b983080993a8ac5d88be145309e7409d4782b04 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 23:17:13 -0400 Subject: [PATCH 16/52] fixed more util errors --- src/utils/logs.ts | 2 +- src/utils/pgsqlDriver.ts | 196 +++++++++++++++++++-------------------- 2 files changed, 99 insertions(+), 99 deletions(-) diff --git a/src/utils/logs.ts b/src/utils/logs.ts index 8f7af431..aa38a733 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -49,7 +49,7 @@ type log = { ticketId?: string; } - +// eslint-disable-next-line no-unused-vars export const log = async(logs: log, client: DiscordClient) => { if (!client.config.logs) return; if (!client.config.logsChannelId) return; diff --git a/src/utils/pgsqlDriver.ts b/src/utils/pgsqlDriver.ts index 361f2486..f9b41783 100644 --- a/src/utils/pgsqlDriver.ts +++ b/src/utils/pgsqlDriver.ts @@ -10,102 +10,102 @@ import { Client, ClientConfig } from "pg"; export class PostgresDriver { - private static instance: PostgresDriver; - private config: ClientConfig; - private conn: Client | undefined; - - constructor(config: ClientConfig) { - this.config = config; - } - - static createSingleton(config: ClientConfig): PostgresDriver { - if (!this.instance) this.instance = new PostgresDriver(config); - return this.instance; - } - - async connect(): Promise { - this.conn = new Client(this.config); - await this.conn.connect(); - } - - async disconnect(): Promise { - this.checkConnection(); - await this.conn!.end(); - } - - private checkConnection(): void { - if (!this.conn) { - throw new Error("No connection to postgres database"); - } - } - - async prepare(table: string): Promise { - this.checkConnection(); - await this.conn!.query( - `CREATE TABLE IF NOT EXISTS ${table} (id VARCHAR(255), value TEXT)` - ); - } - - async getAllRows(table: string): Promise<{ id: string; value: any }[]> { - this.checkConnection(); - const queryResult = await this.conn!.query(`SELECT * FROM ${table}`); - return queryResult.rows.map((row) => ({ - id: row.id, - value: JSON.parse(row.value), - })); - } - - async getRowByKey( - table: string, - key: string - ): Promise<[T | null, boolean]> { - this.checkConnection(); - const queryResult = await this.conn!.query( - `SELECT value FROM ${table} WHERE id = $1`, - [key] - ); - - if (queryResult.rowCount < 1) return [null, false]; - return [JSON.parse(queryResult.rows[0].value), true]; - } - - async setRowByKey( - table: string, - key: string, - value: any, - update: boolean - ): Promise { - this.checkConnection(); - - const stringifiedValue = JSON.stringify(value); - - if (update) { - await this.conn!.query( - `UPDATE ${table} SET value = $1 WHERE id = $2`, - [stringifiedValue, key] - ); - } else { - await this.conn!.query( - `INSERT INTO ${table} (id, value) VALUES ($1, $2)`, - [key, stringifiedValue] - ); - } - - return value; - } - - async deleteAllRows(table: string): Promise { - this.checkConnection(); - const queryResult = await this.conn!.query(`DELETE FROM ${table}`); - return queryResult.rowCount; - } - - async deleteRowByKey(table: string, key: string): Promise { - this.checkConnection(); - const queryResult = await this.conn!.query( - `DELETE FROM ${table} WHERE id = $1`, - [key] - ); - return queryResult.rowCount; - } + private static instance: PostgresDriver; + private config: ClientConfig; + private conn: Client | undefined; + + constructor(config: ClientConfig) { + this.config = config; + } + + static createSingleton(config: ClientConfig): PostgresDriver { + if (!this.instance) this.instance = new PostgresDriver(config); + return this.instance; + } + + async connect(): Promise { + this.conn = new Client(this.config); + await this.conn.connect(); + } + + async disconnect(): Promise { + this.checkConnection(); + await this.conn!.end(); + } + + private checkConnection(): void { + if (!this.conn) { + throw new Error("No connection to postgres database"); + } + } + + async prepare(table: string): Promise { + this.checkConnection(); + await this.conn!.query( + `CREATE TABLE IF NOT EXISTS ${table} (id VARCHAR(255), value TEXT)` + ); + } + + async getAllRows(table: string): Promise<{ id: string; value: any }[]> { + this.checkConnection(); + const queryResult = await this.conn!.query(`SELECT * FROM ${table}`); + return queryResult.rows.map((row) => ({ + id: row.id, + value: JSON.parse(row.value), + })); + } + + async getRowByKey( + table: string, + key: string + ): Promise<[T | null, boolean]> { + this.checkConnection(); + const queryResult = await this.conn!.query( + `SELECT value FROM ${table} WHERE id = $1`, + [key] + ); + + if (queryResult.rowCount < 1) return [null, false]; + return [JSON.parse(queryResult.rows[0].value), true]; + } + + async setRowByKey( + table: string, + key: string, + value: any, + update: boolean + ): Promise { + this.checkConnection(); + + const stringifiedValue = JSON.stringify(value); + + if (update) { + await this.conn!.query( + `UPDATE ${table} SET value = $1 WHERE id = $2`, + [stringifiedValue, key] + ); + } else { + await this.conn!.query( + `INSERT INTO ${table} (id, value) VALUES ($1, $2)`, + [key, stringifiedValue] + ); + } + + return value; + } + + async deleteAllRows(table: string): Promise { + this.checkConnection(); + const queryResult = await this.conn!.query(`DELETE FROM ${table}`); + return queryResult.rowCount; + } + + async deleteRowByKey(table: string, key: string): Promise { + this.checkConnection(); + const queryResult = await this.conn!.query( + `DELETE FROM ${table} WHERE id = $1`, + [key] + ); + return queryResult.rowCount; + } } \ No newline at end of file From 91db9ee3d7cc87bd76d3e7af47f6bb6ffdf2c443 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 23:21:58 -0400 Subject: [PATCH 17/52] Removed unused import --- src/utils/close.ts | 2 +- src/utils/createTicket.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/utils/close.ts b/src/utils/close.ts index 0487098f..6df3bf86 100644 --- a/src/utils/close.ts +++ b/src/utils/close.ts @@ -1,7 +1,7 @@ import { generateMessages } from "ticket-bot-transcript-uploader"; import zlib from "zlib"; import axios from "axios"; -import { ActionRow, ActionRowBuilder, ButtonBuilder, ButtonComponent, ButtonStyle, Collection, CommandInteraction, ComponentBuilder, ComponentType, EmbedBuilder, GuildMember, Message, ModalSubmitInteraction, StringSelectMenuBuilder, TextChannel } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Collection, CommandInteraction, ComponentType, EmbedBuilder, GuildMember, Message, ModalSubmitInteraction, TextChannel } from "discord.js"; import { DiscordClient } from "../Types"; import { log } from "./logs"; let domain = "https://ticket.pm/"; diff --git a/src/utils/createTicket.ts b/src/utils/createTicket.ts index b4e2a6f8..e1a2e407 100644 --- a/src/utils/createTicket.ts +++ b/src/utils/createTicket.ts @@ -1,6 +1,6 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, Collection, EmbedBuilder, ModalSubmitInteraction, TextInputComponent } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Collection, EmbedBuilder, ModalSubmitInteraction, PermissionFlagsBits, TextInputComponent } from "discord.js"; import { DiscordClient } from "../Types"; -import {TicketType} from '../Types'; +import {TicketType} from "../Types"; import { log } from "./logs"; /* @@ -30,8 +30,8 @@ export const createTicket = async (interaction: ModalSubmitInteraction, client: return new Promise(async function (resolve, reject) { await interaction.deferReply({ ephemeral: true }).catch((e) => console.log(e)); - let reason: string[] = []; - let allReasons: string = ""; + const reason: string[] = []; + let allReasons = ""; if (typeof reasons === "object") { reasons.forEach(async (r) => { @@ -60,7 +60,7 @@ export const createTicket = async (interaction: ModalSubmitInteraction, client: permissionOverwrites: [ { id: interaction.guild!.roles.everyone, - deny: [Discord.PermissionFlagsBits.ViewChannel], + deny: [PermissionFlagsBits.ViewChannel], }, ], }); From f7614c56cc0709d9b6aeac3d50c5691260a0b408 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Wed, 21 Jun 2023 23:27:25 -0400 Subject: [PATCH 18/52] Removed unused import --- src/utils/close.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/close.ts b/src/utils/close.ts index 6df3bf86..728f44d6 100644 --- a/src/utils/close.ts +++ b/src/utils/close.ts @@ -1,7 +1,7 @@ import { generateMessages } from "ticket-bot-transcript-uploader"; import zlib from "zlib"; import axios from "axios"; -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Collection, CommandInteraction, ComponentType, EmbedBuilder, GuildMember, Message, ModalSubmitInteraction, TextChannel } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Collection, CommandInteraction, ComponentType, EmbedBuilder, GuildMember, Message, ModalSubmitInteraction, TextChannel } from "discord.js"; import { DiscordClient } from "../Types"; import { log } from "./logs"; let domain = "https://ticket.pm/"; From 8954921806480e1258d417bfa9a9edd2d648772d Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Thu, 22 Jun 2023 16:34:57 -0400 Subject: [PATCH 19/52] Converted interactionCreate --- src/events/interactionCreate.ts | 40 ++++++++++++++++----------------- src/utils/close.ts | 4 ++-- src/utils/close_askReason.ts | 4 ++-- src/utils/createTicket.ts | 5 +++-- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 7b16b2dc..4aa40f7a 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -1,8 +1,11 @@ -import { ActionRowBuilder, GuildChannel, GuildMember, Interaction, ModalBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; +import { ActionRowBuilder, GuildChannel, GuildMember, Interaction, ModalBuilder, SelectMenuComponentOptionData, StringSelectMenuBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; import { DiscordClient } from "../Types"; import { log } from "../utils/logs"; -import {createTicket} from '../utils/createTicket'; -import { close } from '../utils/close'; +import {createTicket} from "../utils/createTicket"; +import { close } from "../utils/close"; +import { claim } from "../utils/claim"; +import { closeAskReason } from "../utils/close_askReason"; +import { deleteTicket } from "../utils/delete"; /* Copyright 2023 Sayrix (github.com/Sayrix) @@ -20,7 +23,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -module.exports = { +export default { name: "interactionCreate", once: false, /** @@ -34,7 +37,7 @@ module.exports = { // Max ticket opened - for (let role of client.config.rolesWhoCanNotCreateTickets) { + for (const role of client.config.rolesWhoCanNotCreateTickets) { if (role && (interaction.member as GuildMember | null)?.roles.cache.has(role)) { return interaction .editReply({ @@ -58,14 +61,13 @@ module.exports = { } // Make a select menus of all tickets types + let options: SelectMenuComponentOptionData[] = []; - let options = []; - - for (let x of client.config.ticketTypes) { + for (const x of client.config.ticketTypes) { // x.cantAccess is an array of roles id // If the user has one of the roles, he can't access to this ticket type - const a: {[key: string]: string} = { + const a: SelectMenuComponentOptionData = { label: x.name, value: x.codeName, }; @@ -74,10 +76,10 @@ module.exports = { options.push(a); } - for (let x of options) { - let option = client.config.ticketTypes.filter((y) => y.codeName === x.value)[0]; + for (const x of options) { + const option = client.config.ticketTypes.filter((y) => y.codeName === x.value)[0]; if (option.cantAccess) { - for (let role of option.cantAccess) { + for (const role of option.cantAccess) { if (role && (interaction.member as GuildMember | null)?.roles.cache.has(role)) { options = options.filter((y) => y.value !== x.value); } @@ -89,8 +91,8 @@ module.exports = { content: client.locales.noTickets }); - const row = new Discord.ActionRowBuilder().addComponents( - new Discord.StringSelectMenuBuilder() + const row = new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() .setCustomId("selectTicketType") .setPlaceholder(client.locales.other.selectTicketTypePlaceholder) .setMaxValues(1) @@ -105,23 +107,19 @@ module.exports = { } if (interaction.customId === "claim") { - const { claim } = require("../utils/claim.js"); claim(interaction, client); } if (interaction.customId === "close") { await interaction.deferReply({ ephemeral: true }).catch((e) => console.log(e)); - const { close } = require("../utils/close.js"); close(interaction, client, client.locales.other.noReasonGiven); } if (interaction.customId === "close_askReason") { - const { closeAskReason } = require("../utils/close_askReason.js"); closeAskReason(interaction, client); } if (interaction.customId === "deleteTicket") { - const { deleteTicket } = require("../utils/delete.js"); deleteTicket(interaction, client); } } @@ -161,7 +159,7 @@ module.exports = { await interaction.showModal(modal).catch((e) => console.log(e)); } else { - require("../utils/createTicket.js").createTicket(interaction, client, ticketType, client.locales.other.noReasonGiven); + createTicket(interaction, client, ticketType, client.locales.other.noReasonGiven); } } @@ -201,8 +199,8 @@ module.exports = { if (interaction.customId === "askReason") { const type = interaction.fields.fields.first()?.customId.split("_")[1]; const ticketType = client.config.ticketTypes.find((x) => x.codeName === type); - //@ts-ignore Remove illegal usages without breaking compatibility - if (!ticketType) return console.error(`Ticket type ${interaction.values[0]} not found!`); + // Using customId until the value can be figured out + if (!ticketType) return console.error(`Ticket type ${interaction.customId} not found!`); createTicket(interaction, client, ticketType, interaction.fields.fields); } diff --git a/src/utils/close.ts b/src/utils/close.ts index 728f44d6..1204b5f9 100644 --- a/src/utils/close.ts +++ b/src/utils/close.ts @@ -1,7 +1,7 @@ import { generateMessages } from "ticket-bot-transcript-uploader"; import zlib from "zlib"; import axios from "axios"; -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Collection, CommandInteraction, ComponentType, EmbedBuilder, GuildMember, Message, ModalSubmitInteraction, TextChannel } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, Collection, CommandInteraction, ComponentType, EmbedBuilder, GuildMember, Message, ModalSubmitInteraction, TextChannel } from "discord.js"; import { DiscordClient } from "../Types"; import { log } from "./logs"; let domain = "https://ticket.pm/"; @@ -21,7 +21,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -export async function close(interaction: ModalSubmitInteraction | CommandInteraction, client: DiscordClient, reason?: string) { +export async function close(interaction: ButtonInteraction | CommandInteraction | ModalSubmitInteraction, client: DiscordClient, reason?: string) { if (!client.config.createTranscript) domain = client.locales.other.unavailable; const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); diff --git a/src/utils/close_askReason.ts b/src/utils/close_askReason.ts index 3eaa9dd0..0f8205ab 100644 --- a/src/utils/close_askReason.ts +++ b/src/utils/close_askReason.ts @@ -14,10 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { ActionRowBuilder, CommandInteraction, GuildMember, ModalBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; +import { ActionRowBuilder, ButtonInteraction, CommandInteraction, GuildMember, ModalBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; import { DiscordClient } from "../Types"; -export const closeAskReason = async(interaction: CommandInteraction, client: DiscordClient) => { +export const closeAskReason = async(interaction: CommandInteraction | ButtonInteraction, client: DiscordClient) => { if ( client.config.whoCanCloseTicket === "STAFFONLY" && !(interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id)) diff --git a/src/utils/createTicket.ts b/src/utils/createTicket.ts index e1a2e407..899768d6 100644 --- a/src/utils/createTicket.ts +++ b/src/utils/createTicket.ts @@ -1,4 +1,4 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Collection, EmbedBuilder, ModalSubmitInteraction, PermissionFlagsBits, TextInputComponent } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Collection, EmbedBuilder, ModalSubmitInteraction, PermissionFlagsBits, StringSelectMenuInteraction, TextInputComponent } from "discord.js"; import { DiscordClient } from "../Types"; import {TicketType} from "../Types"; import { log } from "./logs"; @@ -25,7 +25,7 @@ limitations under the License. * @param {Object} ticketType * @param {Object|string} reasons */ -export const createTicket = async (interaction: ModalSubmitInteraction, client: DiscordClient, ticketType: TicketType, reasons?: Collection) => { +export const createTicket = async (interaction: StringSelectMenuInteraction | ModalSubmitInteraction, client: DiscordClient, ticketType: TicketType, reasons?: Collection | string) => { // eslint-disable-next-line no-async-promise-executor return new Promise(async function (resolve, reject) { await interaction.deferReply({ ephemeral: true }).catch((e) => console.log(e)); @@ -39,6 +39,7 @@ export const createTicket = async (interaction: ModalSubmitInteraction, client: }); allReasons = reason.map((r, i) => `Question ${i + 1}: ${r}`).join(", "); } + if(typeof reasons === "string") allReasons = reasons; let ticketName = ""; From 13e35d2e9dc8d06fe0c7e08556e161d8ba6ce4ac Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Thu, 22 Jun 2023 16:36:42 -0400 Subject: [PATCH 20/52] Check for interaction guild to avoid ! assertation --- src/utils/createTicket.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/utils/createTicket.ts b/src/utils/createTicket.ts index 899768d6..7cc0f5a4 100644 --- a/src/utils/createTicket.ts +++ b/src/utils/createTicket.ts @@ -54,13 +54,14 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo .replace("USERID", interaction.user.id) .replace("TICKETCOUNT", (await client.db.get("temp.ticketCount")) ?? "0"); } - + if(!interaction.guild) return console.error("Interaction createTicket was not executed in a guild"); + const channel = await client.guilds.cache.get(client.config.guildId)?.channels.create({ name: ticketName, parent: ticketType.categoryId, permissionOverwrites: [ { - id: interaction.guild!.roles.everyone, + id: interaction.guild.roles.everyone, deny: [PermissionFlagsBits.ViewChannel], }, ], From 9ab69e625994d9d3e9797838c63ff4aa8709f49a Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Thu, 22 Jun 2023 17:12:31 -0400 Subject: [PATCH 21/52] option to disable telemetry and warns user #148 --- config/config.example.jsonc | 4 +- src/events/ready.ts | 128 ++++++++++++++++++++++-------------- 2 files changed, 83 insertions(+), 49 deletions(-) diff --git a/config/config.example.jsonc b/config/config.example.jsonc index 7efa26a9..794faca3 100644 --- a/config/config.example.jsonc +++ b/config/config.example.jsonc @@ -108,5 +108,7 @@ "status": "online" // online, idle, dnd, invisible set to online if the type is STREAMING }, - "maxTicketOpened": 0 // The number of tickets the user can open while another one is already open. Set to 0 to unlimited + "maxTicketOpened": 0, // The number of tickets the user can open while another one is already open. Set to 0 to unlimited + + "disableTelemetry": false } diff --git a/src/events/ready.ts b/src/events/ready.ts index 32d95b8d..75e0f770 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,8 +1,12 @@ /* eslint-disable no-unused-vars */ -const readline = require("readline"); -const axios = require("axios"); -const Discord = require("discord.js"); -const WebSocketClient = require("websocket").client; +import readline from "readline"; +import axios from "axios"; +import {client as WebSocketClient, connection} from "websocket"; +import { DiscordClient, SayrixSponsorData } from "../Types"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; +import {version} from "../../package.json"; +import os from "os"; +import deployCmd from "../deploy-commands"; /* Copyright 2023 Sayrix (github.com/Sayrix) @@ -20,21 +24,21 @@ See the License for the specific language governing permissions and limitations under the License. */ -module.exports = { +export default { name: "ready", once: true, /** * @param {Discord.Client} client */ - async execute(client) { + async execute(client: DiscordClient) { if (!client.config.guildId) { console.log("⚠️⚠️⚠️ Please add the guild id in the config.jsonc file. ⚠️⚠️⚠️"); process.exit(0); } await client.guilds.fetch(client.config.guildId); - await client.guilds.cache.get(client.config.guildId).members.fetch(); - if (!client.guilds.cache.get(client.config.guildId).members.me.permissions.has("Administrator")) { + await client.guilds.cache.get(client.config.guildId)?.members.fetch(); + if (!client.guilds.cache.get(client.config.guildId)?.members.me?.permissions.has("Administrator")) { console.log("\n⚠️⚠️⚠️ I don't have the Administrator permission, to prevent any issues please add the Administrator permission to me. ⚠️⚠️⚠️"); process.exit(0); } @@ -55,7 +59,7 @@ module.exports = { process.exit(0); } - let embed = client.embeds.openTicket; + const embed = client.locales.embeds.openTicket; /* Copyright 2023 Sayrix (github.com/Sayrix) @@ -75,7 +79,7 @@ module.exports = { embed.color = parseInt(client.config.mainColor, 16); // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) - embed.footer.text = "ticket.pm" + client.embeds.ticketOpened.footer.text.replace("ticket.pm", ""); // Please respect the LICENSE :D + embed.footer.text = "ticket.pm" + embed.footer.text.replace("ticket.pm", ""); // Please respect the LICENSE :D // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) /* @@ -94,27 +98,26 @@ module.exports = { limitations under the License. */ - const row = new Discord.ActionRowBuilder().addComponents( - new Discord.ButtonBuilder().setCustomId("openTicket").setLabel(client.locales.other.openTicketButtonMSG).setStyle(Discord.ButtonStyle.Primary) + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder().setCustomId("openTicket").setLabel(client.locales.other.openTicketButtonMSG).setStyle(ButtonStyle.Primary) ); try { - const msg = await openTicketChannel?.messages?.fetch(embedMessageId).catch(() => {}); + const msg = await openTicketChannel?.messages?.fetch(embedMessageId).catch((ex) => console.error(ex)); if (msg && msg.id) { msg.edit({ embeds: [embed], components: [row] }); } else { - client.channels.cache - .get(client.config.openTicketChannelId) - .send({ - embeds: [embed], - components: [row] - }) - .then((msg) => { - client.db.set("temp.openTicketMessageId", msg.id); - }); + const channel = client.channels.cache.get(client.config.openTicketChannelId); + if(!channel || !channel.isTextBased()) return console.error("Invalid openTicketChannelId"); + channel.send({ + embeds: [embed], + components: [row] + }).then((rMsg) => { + client.db.set("temp.openTicketMessageId", rMsg.id); + }); } } catch (e) { console.error(e); @@ -124,22 +127,34 @@ module.exports = { if (client.config.status) { if (!client.config.status.enabled) return; - let type = client.config.status.type; - if (type === "PLAYING") type = 0; - if (type === "STREAMING") type = 1; - if (type === "LISTENING") type = 2; - if (type === "WATCHING") type = 3; - if (type === "COMPETING") type = 5; + let type = 0; + switch(client.config.status.type) { + case "PLAYING": + type = 0; + break; + case "STREAMING": + type = 1; + break; + case "LISTENING": + type = 2; + break; + case "WATCHING": + type = 3; + break; + case "COMPETING": + type = 4; + break; + } if (client.config.status.type && client.config.status.text) { // If the user just want to set the status but not the activity const url = client.config.status.url; - client.user.setPresence({ + client.user?.setPresence({ activities: [{ name: client.config.status.text, type: type, url: (url && url.trim() !== "") ? url : undefined }], status: client.config.status.status, }); } - client.user.setStatus(client.config.status.status); + client.user?.setStatus(client.config.status.status); } } @@ -148,24 +163,24 @@ module.exports = { readline.cursorTo(process.stdout, 0); process.stdout.write( - `\x1b[0m🚀 The bot is ready! Logged in as \x1b[37;46;1m${client.user.tag}\x1b[0m (\x1b[37;46;1m${client.user.id}\x1b[0m) + `\x1b[0m🚀 The bot is ready! Logged in as \x1b[37;46;1m${client.user?.tag}\x1b[0m (\x1b[37;46;1m${client.user?.id}\x1b[0m) \x1b[0m🌟 You can leave a star on GitHub: \x1b[37;46;1mhttps://github.com/Sayrix/ticket-bot \x1b[0m \x1b[0m📖 Documentation: \x1b[37;46;1mhttps://doc.ticket.pm \x1b[0m \x1b[0m⛅ Host your ticket-bot by being a sponsor from 1$/month: \x1b[37;46;1mhttps://github.com/sponsors/Sayrix \x1b[0m\n`.replace(/\t/g, "") ); - const a = await axios.get("https://raw.githubusercontent.com/Sayrix/sponsors/main/sponsors.json").catch(() => {}); + const a = await axios.get("https://raw.githubusercontent.com/Sayrix/sponsors/main/sponsors.json").catch(() => {return;}); if (a) { - const sponsors = a.data; + const sponsors = a.data as SayrixSponsorData[]; const sponsorsList = sponsors .map((s) => `\x1b]8;;https://github.com/${s.sponsor.login}\x1b\\\x1b[1m${s.sponsor.login}\x1b]8;;\x1b\\\x1b[0m`) .join(", "); process.stdout.write(`\x1b[0m💖 Thanks to our sponsors: ${sponsorsList}\n`); } - let connected; + let connected = false; - function telemetry(connection) { + function telemetry(connection: connection) { connection.sendUTF( JSON.stringify({ type: "telemetry", @@ -175,20 +190,20 @@ module.exports = { users: client?.users?.cache?.size }, infos: { - ticketbotVersion: require("../package.json").version, + ticketbotVersion: version, nodeVersion: process.version, - os: require("os").platform(), - osVersion1: require("os").release(), - osVersion2: require("os").version(), + os: os.platform(), + osVersion1: os.release(), + osVersion2: os.version(), uptime: process.uptime(), ram: { - total: require("os").totalmem(), - free: require("os").freemem() + total: os.totalmem(), + free: os.freemem() }, cpu: { - model: require("os").cpus()[0].model, - cores: require("os").cpus().length, - arch: require("os").arch() + model: os.cpus()[0].model, + cores: os.cpus().length, + arch: os.arch() } }, clientName: client?.user?.tag, @@ -201,7 +216,7 @@ module.exports = { async function connect() { if (connected) return; - let ws = new WebSocketClient(); + const ws = new WebSocketClient(); ws.on("connectFailed", (e) => { connected = false; @@ -233,9 +248,26 @@ module.exports = { ws.connect("wss://ws.ticket.pm", "echo-protocol"); } - - connect(); - require("../deploy-commands").deployCommands(client); + if(!client.config.disableTelemetry) { + console.warn(` + PRIVACY NOTICES + ------------------------------- + Telemetry is current enabled and the following information are sent to the server anonymously: + * Discord Bot's number of guilds & users + * Current Source Version + * NodeJS Version + * OS Version + * CPU version, name, core count, architecture, and model + * Current Process up-time + * System total ram and freed ram + * Client name and id + * Guild ID + ------------------------------- + If you do not wish to send this ifnormation, please set "disableTelemetry" to true in the config + `); + connect(); + } + deployCmd.deployCommands(); } }; From bc35c7a93a483752441b6842166ec0fa548de6f9 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Thu, 22 Jun 2023 17:13:14 -0400 Subject: [PATCH 22/52] Types update --- package.json | 1 + src/Types.ts | 24 +++++++++++++++++++++++- src/deploy-commands.ts | 1 - 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9fa44c04..811afa9b 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@types/fs-extra": "^11.0.1", "@types/node": "^20.3.1", "@types/pg": "^8.10.2", + "@types/websocket": "^1.0.5", "@typescript-eslint/eslint-plugin": "^5.60.0", "@typescript-eslint/parser": "^5.60.0", "eslint": "^8.43.0", diff --git a/src/Types.ts b/src/Types.ts index 189ef43b..46ea51c4 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -58,16 +58,18 @@ export type config = { status: { enabled: boolean; text: string; - type: "PLAYING" | "WATCHING" | "LISTENING" | "STREAMING" | "COMPETING", + type: "PLAYING" | "STREAMING"| "LISTENING" | "WATCHING" | "COMPETING", url?: string, status: "online" }, maxTicketOpened: number; + disableTelemetry: boolean; } export type locale = { embeds: { openTicket: { title: string, + color?: number, description: string, footer: { text: string @@ -152,3 +154,23 @@ export interface DiscordClient extends Client { msToHm: (ms: number | Date) => string; commands: Collection; } + +export type SayrixSponsorData = { + sponsor: { + login: string; + name: string; + avatarUrl: string; + websiteUrl?: string; + linkUrl: string; + type: string; + avatarUrlHighRes: string; + avatarUrlMediumRes: string; + avatarUrlLowRes: string; + }, + isOneTime: boolean; + monthlyDollars: number; + privacyLevel: string; + tierName: string; + createdAt: string; + provider: string; +} \ No newline at end of file diff --git a/src/deploy-commands.ts b/src/deploy-commands.ts index 875729f2..cdd234c4 100644 --- a/src/deploy-commands.ts +++ b/src/deploy-commands.ts @@ -4,7 +4,6 @@ import { jsonc } from "jsonc"; // eslint-disable-next-line node/no-extraneous-require import { REST } from "@discordjs/rest"; import { Routes } from "discord.js"; -// eslint-disable-next-line no-unused-vars // eslint-disable-next-line node/no-missing-require, node/no-unpublished-require import { token } from "../config/token.json"; From 8175a28de48d3433b73c78f1e453290437de06ac Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Thu, 22 Jun 2023 17:14:55 -0400 Subject: [PATCH 23/52] fixed some lint issues --- src/index.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index e9a486fa..e00372ba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,13 +15,13 @@ limitations under the License. */ import { Interaction } from "discord.js"; -import fs from 'fs-extra'; -import path from 'node:path'; -import { Client, Collection, GatewayIntentBits } from 'discord.js' +import fs from "fs-extra"; +import path from "node:path"; +import { Client, Collection, GatewayIntentBits } from "discord.js"; // eslint-disable-next-line node/no-missing-require, node/no-unpublished-require -import { token } from '../config/token.json' -import {QuickDB, MySQLDriver } from 'quick.db' -import { jsonc } from 'jsonc'; +import { token } from "../config/token.json"; +import {QuickDB, MySQLDriver } from "quick.db"; +import { jsonc } from "jsonc"; import { DiscordClient, config, locale } from "./Types"; // Although invalid type, it should be good enough for now until more stuff needs to be handled here @@ -73,7 +73,7 @@ const client = new Client({ }) as DiscordClient; // All variables stored in the client object -client.config = config +client.config = config; let db: QuickDB | undefined; From 35ff035cee88788ff7c18023959917c8c8450c4a Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Thu, 22 Jun 2023 20:11:15 -0400 Subject: [PATCH 24/52] Don't run action when PR is in draft & Run npm i during PR for full CI experience --- .github/workflows/builder.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 817c871c..00abb7b9 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -5,6 +5,7 @@ on: branches: - "main" pull_request: + types: [review_requested, ready_for_review] permissions: contents: read @@ -35,7 +36,7 @@ jobs: key: node_modules-${{ matrix.os }}-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }} - name: npm i - if: steps.cache-node_modules.outputs.cache-hit != 'true' + if: steps.cache-node_modules.outputs.cache-hit != 'true' || github.event_name == 'pull_request' run: npm i --no-audit - name: ESLint From f4d7c4e7744f3c3dd188c69c1dc4bd9e6c7898b1 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Thu, 22 Jun 2023 23:47:40 -0400 Subject: [PATCH 25/52] fixed some eslint stuff --- .eslintrc.json | 2 +- src/deploy-commands.ts | 3 +-- src/index.ts | 16 +++++++--------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 407f642a..35b11843 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,7 +4,7 @@ "node": true }, "overrides": [], - "extends": ["eslint:recommended", "plugin:node/recommended", "prettier", "plugin:@typescript-eslint/recommended"], + "extends": ["eslint:recommended", "prettier", "plugin:@typescript-eslint/recommended"], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": "latest", diff --git a/src/deploy-commands.ts b/src/deploy-commands.ts index cdd234c4..618f180e 100644 --- a/src/deploy-commands.ts +++ b/src/deploy-commands.ts @@ -1,10 +1,8 @@ import fs from "node:fs"; import path from "node:path"; import { jsonc } from "jsonc"; -// eslint-disable-next-line node/no-extraneous-require import { REST } from "@discordjs/rest"; import { Routes } from "discord.js"; -// eslint-disable-next-line node/no-missing-require, node/no-unpublished-require import { token } from "../config/token.json"; export default { @@ -20,6 +18,7 @@ export default { for (const file of commandFiles) { const filePath = path.join(commandsPath, file); + // eslint-disable-next-line @typescript-eslint/no-var-requires const command = require(filePath); commands.push(command.data.toJSON()); } diff --git a/src/index.ts b/src/index.ts index e00372ba..2c59880d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,11 +18,12 @@ import { Interaction } from "discord.js"; import fs from "fs-extra"; import path from "node:path"; import { Client, Collection, GatewayIntentBits } from "discord.js"; -// eslint-disable-next-line node/no-missing-require, node/no-unpublished-require import { token } from "../config/token.json"; import {QuickDB, MySQLDriver } from "quick.db"; import { jsonc } from "jsonc"; import { DiscordClient, config, locale } from "./Types"; +import {version} from "../package.json"; +import {PostgresDriver} from "./utils/pgsqlDriver"; // Although invalid type, it should be good enough for now until more stuff needs to be handled here process.on("unhandledRejection", (reason: string, promise: string, a: string) => { @@ -47,9 +48,7 @@ fetch("https://api.github.com/repos/Sayrix/Ticket-Bot/tags").then((res) => { res.json().then((json) => { // Assumign the format stays consistent (i.e. x.x.x) const latest = json[0].name.split(".").map((k: string) => parseInt(k)); - //@ts-ignore allow access to package.json for version check - const current = require("../package.json") - .version.split(".") + const current = version.split(".") .map((k: string) => parseInt(k)); if ( latest[0] > current[0] || @@ -75,19 +74,17 @@ const client = new Client({ // All variables stored in the client object client.config = config; -let db: QuickDB | undefined; +let db: QuickDB | undefined; if (client.config.postgre?.enabled) { // PostgreSQL Support. (async () => { try { - // eslint-disable-next-line node/no-missing-require require.resolve("pg"); } catch (e) { console.error("pg driver is not installed!\n\nPlease run \"npm i pg\" in the console!"); throw e; } - const PostgresDriver = require("./utils/pgsqlDriver"); const pgsql = new PostgresDriver({ host: client.config.postgre?.host, user: client.config.postgre?.user, @@ -107,7 +104,6 @@ if (client.config.postgre?.enabled) { // MySQL Support (async () => { try { - // eslint-disable-next-line node/no-missing-require require.resolve("mysql2"); } catch (e) { console.error("mysql2 is not installed!\n\nPlease run \"npm i mysql2\" in the console!"); @@ -135,7 +131,7 @@ if (client.config.postgre?.enabled) { db = new QuickDB(); client.db = db; } - +// eslint-disable-next-line @typescript-eslint/no-var-requires client.locales = require(`./locales/${client.config.lang}.json`) as locale; client.msToHm = function dhm(ms: number | Date) { if(ms instanceof Date) ms = ms.getTime(); @@ -162,6 +158,7 @@ const commandFiles = fs.readdirSync(commandsPath).filter((file) => file.endsWith for (const file of commandFiles) { const filePath = path.join(commandsPath, file); + // eslint-disable-next-line @typescript-eslint/no-var-requires const command = require(filePath); client.commands.set(command.data.name, command); } @@ -189,6 +186,7 @@ const eventFiles = fs.readdirSync(eventsPath).filter((file) => file.endsWith(".j for (const file of eventFiles) { const filePath = path.join(eventsPath, file); + // eslint-disable-next-line @typescript-eslint/no-var-requires const event = require(filePath); if (event.once) { client.once(event.name, (...args) => event.execute(...args)); From 16401f02bb886889e700c935d40f2c31f294e21c Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Thu, 22 Jun 2023 23:49:07 -0400 Subject: [PATCH 26/52] fixed psqlDriver eslint --- src/utils/pgsqlDriver.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/pgsqlDriver.ts b/src/utils/pgsqlDriver.ts index f9b41783..912ee5e4 100644 --- a/src/utils/pgsqlDriver.ts +++ b/src/utils/pgsqlDriver.ts @@ -6,7 +6,6 @@ * LICENSE: https://github.com/plexidev/quick.db/blob/dev/LICENSE.md */ -// eslint-disable-next-line node/no-missing-require import { Client, ClientConfig } from "pg"; export class PostgresDriver { From f84c0a6fab2389ccda3bbb0c2e7d3ea5b9ec3008 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Fri, 23 Jun 2023 12:16:07 -0400 Subject: [PATCH 27/52] fix: Resolves #167 --- src/events/ready.ts | 3 ++- src/utils/close.ts | 3 ++- src/utils/createTicket.ts | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/events/ready.ts b/src/events/ready.ts index 75e0f770..106bd8e4 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -79,7 +79,8 @@ export default { embed.color = parseInt(client.config.mainColor, 16); // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) - embed.footer.text = "ticket.pm" + embed.footer.text.replace("ticket.pm", ""); // Please respect the LICENSE :D + const footer = embed.footer.text.replace("ticket.pm", ""); + embed.footer.text = `ticket.pm ${footer.trim() !== "" ? `- ${footer}` : ""}`; // Please respect the LICENSE :D // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) /* diff --git a/src/utils/close.ts b/src/utils/close.ts index 1204b5f9..6862cd80 100644 --- a/src/utils/close.ts +++ b/src/utils/close.ts @@ -145,6 +145,7 @@ export async function close(interaction: ButtonInteraction | CommandInteraction }) .catch((e) => console.log(e)); + const footer = lEmbed.ticketClosedDM.footer.text.replace("ticket.pm", ""); const tiketClosedDMEmbed = new EmbedBuilder() .setColor(`#${lEmbed.ticketClosedDM.color ? lEmbed.ticketClosedDM.color : client.config.mainColor}`) .setDescription( @@ -173,7 +174,7 @@ export async function close(interaction: ButtonInteraction | CommandInteraction .setFooter({ // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) - text: "ticket.pm" + lEmbed.ticketClosedDM.footer.text.replace("ticket.pm", ""), // Please respect the LICENSE :D + text: `ticket.pm ${footer.trim() !== "" ? `- ${footer}` : ""}`, // Please respect the LICENSE :D // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) iconURL: lEmbed.ticketClosedDM.footer.iconUrl }); diff --git a/src/utils/createTicket.ts b/src/utils/createTicket.ts index 7cc0f5a4..3141fae6 100644 --- a/src/utils/createTicket.ts +++ b/src/utils/createTicket.ts @@ -119,6 +119,7 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo }); } const lEmbeds = client.locales.embeds; + const footer = lEmbeds.ticketOpened.footer.text.replace("ticket.pm", ""); const ticketOpenedEmbed = new EmbedBuilder() .setColor(`#${ticketType.color ? ticketType.color : client.config.mainColor}`) .setTitle(lEmbeds.ticketOpened.title.replace("CATEGORYNAME", ticketType.name)) @@ -170,7 +171,7 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo */ .setFooter({ // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) - text: "ticket.pm" + lEmbeds.ticketOpened.footer.text.replace("ticket.pm", ""), // Please respect the LICENSE :D + text: `ticket.pm ${footer.trim() !== "" ? `- ${footer}` : ""}`, // Please respect the LICENSE :D // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) iconURL: lEmbeds.ticketOpened.footer.iconUrl }); From ea60619b28329b1f2db8b621f78d5c7fbef4bad9 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Fri, 23 Jun 2023 12:59:44 -0400 Subject: [PATCH 28/52] Refactor: Token uses env variable #160 & #158 seems to be stable enough --- .env.example | 4 ++-- config/config.example.jsonc | 2 +- config/token.example.json | 3 --- {src/locales => locales}/cs.json | 0 {src/locales => locales}/de.json | 0 {src/locales => locales}/es.json | 0 {src/locales => locales}/fr.json | 0 {src/locales => locales}/main.json | 0 {src/locales => locales}/tr.json | 0 package.json | 5 +++-- src/deploy-commands.ts | 9 ++++----- src/events/ready.ts | 4 ++-- src/index.ts | 21 ++++++++++++--------- tsconfig.json | 1 + 14 files changed, 25 insertions(+), 24 deletions(-) delete mode 100644 config/token.example.json rename {src/locales => locales}/cs.json (100%) rename {src/locales => locales}/de.json (100%) rename {src/locales => locales}/es.json (100%) rename {src/locales => locales}/fr.json (100%) rename {src/locales => locales}/main.json (100%) rename {src/locales => locales}/tr.json (100%) diff --git a/.env.example b/.env.example index 02cdd961..8f36cad0 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ # Your discord token -token="" +TOKEN="" # Prisma Database URL (refer to docs for more details) -database_url="postgresql://user:password@localhost:5432/database" \ No newline at end of file +DATABASE_URL="postgresql://user:password@localhost:5432/database" \ No newline at end of file diff --git a/config/config.example.jsonc b/config/config.example.jsonc index 794faca3..027749b2 100644 --- a/config/config.example.jsonc +++ b/config/config.example.jsonc @@ -110,5 +110,5 @@ "maxTicketOpened": 0, // The number of tickets the user can open while another one is already open. Set to 0 to unlimited - "disableTelemetry": false + "disableTelemetry": false // Whether or not to allow anonymous telemetry to help improve the software } diff --git a/config/token.example.json b/config/token.example.json deleted file mode 100644 index 54a69a53..00000000 --- a/config/token.example.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "token": "my.discord.bot.token" -} diff --git a/src/locales/cs.json b/locales/cs.json similarity index 100% rename from src/locales/cs.json rename to locales/cs.json diff --git a/src/locales/de.json b/locales/de.json similarity index 100% rename from src/locales/de.json rename to locales/de.json diff --git a/src/locales/es.json b/locales/es.json similarity index 100% rename from src/locales/es.json rename to locales/es.json diff --git a/src/locales/fr.json b/locales/fr.json similarity index 100% rename from src/locales/fr.json rename to locales/fr.json diff --git a/src/locales/main.json b/locales/main.json similarity index 100% rename from src/locales/main.json rename to locales/main.json diff --git a/src/locales/tr.json b/locales/tr.json similarity index 100% rename from src/locales/tr.json rename to locales/tr.json diff --git a/package.json b/package.json index 811afa9b..2a81d617 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,9 @@ "version": "2.5.0", "description": "Bot with a ticket system using Discord.js v14", "main": "dist/index.js", - "type": "module", "scripts": { "start": "node dist/index.js", - "build": "tsc", + "build": "rimraf dist && tsc", "format:fix": "prettier --write .", "format:check": "prettier --check .", "lint:fix": "eslint --fix .", @@ -31,6 +30,7 @@ "axios": "^1.4.0", "better-sqlite3": "^8.4.0", "discord.js": "^14.11.0", + "dotenv": "^16.3.1", "fs-extra": "^11.1.1", "jsonc": "^2.0.0", "mongoose": "^7.3.0", @@ -38,6 +38,7 @@ "pg": "^8.11.0", "quick.db": "^9.1.6", "readline": "^1.3.0", + "rimraf": "^5.0.1", "ticket-bot-transcript-uploader": "^1.3.0", "websocket": "^1.0.34" }, diff --git a/src/deploy-commands.ts b/src/deploy-commands.ts index 618f180e..b54b3c97 100644 --- a/src/deploy-commands.ts +++ b/src/deploy-commands.ts @@ -3,7 +3,6 @@ import path from "node:path"; import { jsonc } from "jsonc"; import { REST } from "@discordjs/rest"; import { Routes } from "discord.js"; -import { token } from "../config/token.json"; export default { /** @@ -14,16 +13,16 @@ export default { const commandsPath = path.join(__dirname, "commands"); const commandFiles = fs.readdirSync(commandsPath).filter((file) => file.endsWith(".js")); - const { clientId, guildId } = jsonc.parse(fs.readFileSync(path.join(__dirname, "config/config.jsonc"), "utf8")); + const { clientId, guildId } = jsonc.parse(fs.readFileSync(path.join(__dirname, "../config/config.jsonc"), "utf8")); for (const file of commandFiles) { const filePath = path.join(commandsPath, file); // eslint-disable-next-line @typescript-eslint/no-var-requires - const command = require(filePath); + const command = require(filePath).default; commands.push(command.data.toJSON()); } - - const rest = new REST({ version: "10" }).setToken(token); + if(!process.env["TOKEN"]) throw Error("Discord Token Expected, deploy-command"); + const rest = new REST({ version: "10" }).setToken(process.env["TOKEN"]); rest .put(Routes.applicationGuildCommands(clientId, guildId), { body: commands }) diff --git a/src/events/ready.ts b/src/events/ready.ts index 106bd8e4..540643fb 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -4,7 +4,6 @@ import axios from "axios"; import {client as WebSocketClient, connection} from "websocket"; import { DiscordClient, SayrixSponsorData } from "../Types"; import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; -import {version} from "../../package.json"; import os from "os"; import deployCmd from "../deploy-commands"; @@ -191,7 +190,8 @@ export default { users: client?.users?.cache?.size }, infos: { - ticketbotVersion: version, + // eslint-disable-next-line @typescript-eslint/no-var-requires + ticketbotVersion: require("../../package.json").version, nodeVersion: process.version, os: os.platform(), osVersion1: os.release(), diff --git a/src/index.ts b/src/index.ts index 2c59880d..6cf574f0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,12 +18,15 @@ import { Interaction } from "discord.js"; import fs from "fs-extra"; import path from "node:path"; import { Client, Collection, GatewayIntentBits } from "discord.js"; -import { token } from "../config/token.json"; import {QuickDB, MySQLDriver } from "quick.db"; import { jsonc } from "jsonc"; import { DiscordClient, config, locale } from "./Types"; -import {version} from "../package.json"; import {PostgresDriver} from "./utils/pgsqlDriver"; +import { config as envconf } from "dotenv"; + +// Initalize .env file as environment +try {envconf();} +catch(ex) {console.log(".env failed to load");} // Although invalid type, it should be good enough for now until more stuff needs to be handled here process.on("unhandledRejection", (reason: string, promise: string, a: string) => { @@ -48,7 +51,8 @@ fetch("https://api.github.com/repos/Sayrix/Ticket-Bot/tags").then((res) => { res.json().then((json) => { // Assumign the format stays consistent (i.e. x.x.x) const latest = json[0].name.split(".").map((k: string) => parseInt(k)); - const current = version.split(".") + // eslint-disable-next-line @typescript-eslint/no-var-requires + const current = require("../package.json").version.split(".") .map((k: string) => parseInt(k)); if ( latest[0] > current[0] || @@ -61,8 +65,7 @@ fetch("https://api.github.com/repos/Sayrix/Ticket-Bot/tags").then((res) => { }); - -const config: config = jsonc.parse(fs.readFileSync(path.join(__dirname, "config/config.jsonc"), "utf8")); +const config: config = jsonc.parse(fs.readFileSync(path.join(__dirname, "/../config/config.jsonc"), "utf8")); const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMembers], @@ -132,7 +135,7 @@ if (client.config.postgre?.enabled) { client.db = db; } // eslint-disable-next-line @typescript-eslint/no-var-requires -client.locales = require(`./locales/${client.config.lang}.json`) as locale; +client.locales = require(`../locales/${client.config.lang}.json`) as locale; client.msToHm = function dhm(ms: number | Date) { if(ms instanceof Date) ms = ms.getTime(); @@ -159,7 +162,7 @@ const commandFiles = fs.readdirSync(commandsPath).filter((file) => file.endsWith for (const file of commandFiles) { const filePath = path.join(commandsPath, file); // eslint-disable-next-line @typescript-eslint/no-var-requires - const command = require(filePath); + const command = require(filePath).default; client.commands.set(command.data.name, command); } @@ -187,7 +190,7 @@ const eventFiles = fs.readdirSync(eventsPath).filter((file) => file.endsWith(".j for (const file of eventFiles) { const filePath = path.join(eventsPath, file); // eslint-disable-next-line @typescript-eslint/no-var-requires - const event = require(filePath); + const event = require(filePath).default; if (event.once) { client.once(event.name, (...args) => event.execute(...args)); } else { @@ -196,7 +199,7 @@ for (const file of eventFiles) { } // Login the bot -client.login(token); +client.login(process.env["TOKEN"]); /* Copyright 2023 Sayrix (github.com/Sayrix) diff --git a/tsconfig.json b/tsconfig.json index 460279a2..25b5b7ee 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "outDir": "./dist", + "rootDir": "./src/", "allowJs": true, "module": "CommonJS", "target": "ESNext", From 6b3e8e9f208844e495a87964ac437b2d49acfa8d Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Fri, 23 Jun 2023 13:27:00 -0400 Subject: [PATCH 29/52] refactor: Lint script will only check ts file --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2a81d617..9a0e7282 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "build": "rimraf dist && tsc", "format:fix": "prettier --write .", "format:check": "prettier --check .", - "lint:fix": "eslint --fix .", - "lint:check": "eslint ." + "lint:fix": "eslint --fix . --ext .ts", + "lint:check": "eslint . --ext .ts" }, "repository": { "type": "git", From f7c323429b26ad12a86a932233cb9a236948f7b1 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Fri, 23 Jun 2023 17:53:57 -0400 Subject: [PATCH 30/52] Workflow use package.json script to run lint --- .github/workflows/builder.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 00abb7b9..c40c0573 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -40,7 +40,7 @@ jobs: run: npm i --no-audit - name: ESLint - run: npx --no-install eslint . + run: npm run lint:check - name: build run: npm run build --if-present From e96caf389dfe6b1f131fc598c02e320afdc99f27 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Fri, 23 Jun 2023 18:18:18 -0400 Subject: [PATCH 31/52] refactor: Replace disableTelemetry with minimalTracking, request by @Sayrix --- config/config.example.jsonc | 7 ++- src/Types.ts | 2 +- src/events/ready.ts | 87 ++++++++++++++++++++++--------------- 3 files changed, 58 insertions(+), 38 deletions(-) diff --git a/config/config.example.jsonc b/config/config.example.jsonc index 027749b2..a41b03fe 100644 --- a/config/config.example.jsonc +++ b/config/config.example.jsonc @@ -109,6 +109,9 @@ }, "maxTicketOpened": 0, // The number of tickets the user can open while another one is already open. Set to 0 to unlimited - - "disableTelemetry": false // Whether or not to allow anonymous telemetry to help improve the software + /* + Whether or not to minimizing the tracking data that are being sent + Enabling this will cause the telemetry to only send the software version and node version + */ + "minimalTracking": false } diff --git a/src/Types.ts b/src/Types.ts index 46ea51c4..c88eb7c1 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -63,7 +63,7 @@ export type config = { status: "online" }, maxTicketOpened: number; - disableTelemetry: boolean; + minimalTracking: boolean; } export type locale = { embeds: { diff --git a/src/events/ready.ts b/src/events/ready.ts index 540643fb..861837b3 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -181,6 +181,31 @@ export default { let connected = false; function telemetry(connection: connection) { + let fullInfo: {[key:string]: string | number | {[key:string]: string | number}} = { + os: os.platform(), + osVersion1: os.release(), + osVersion2: os.version(), + uptime: process.uptime(), + ram: { + total: os.totalmem(), + free: os.freemem() + }, + cpu: { + model: os.cpus()[0].model, + cores: os.cpus().length, + arch: os.arch() + } + }; + let moreInfo: {[key:string]: string | undefined} = { + clientName: client?.user?.tag, + clientId: client?.user?.id, + guildId: client?.config?.guildId + }; + // Minimal tracking enabled, remove those info from being sent + if(client.config.minimalTracking) { + fullInfo = {}; + moreInfo = {}; + } connection.sendUTF( JSON.stringify({ type: "telemetry", @@ -193,23 +218,9 @@ export default { // eslint-disable-next-line @typescript-eslint/no-var-requires ticketbotVersion: require("../../package.json").version, nodeVersion: process.version, - os: os.platform(), - osVersion1: os.release(), - osVersion2: os.version(), - uptime: process.uptime(), - ram: { - total: os.totalmem(), - free: os.freemem() - }, - cpu: { - model: os.cpus()[0].model, - cores: os.cpus().length, - arch: os.arch() - } + ...fullInfo }, - clientName: client?.user?.tag, - clientId: client?.user?.id, - guildId: client?.config?.guildId + ...moreInfo } }) ); @@ -249,25 +260,31 @@ export default { ws.connect("wss://ws.ticket.pm", "echo-protocol"); } - if(!client.config.disableTelemetry) { - console.warn(` - PRIVACY NOTICES - ------------------------------- - Telemetry is current enabled and the following information are sent to the server anonymously: - * Discord Bot's number of guilds & users - * Current Source Version - * NodeJS Version - * OS Version - * CPU version, name, core count, architecture, and model - * Current Process up-time - * System total ram and freed ram - * Client name and id - * Guild ID - ------------------------------- - If you do not wish to send this ifnormation, please set "disableTelemetry" to true in the config - `); - connect(); - } + if(!client.config.minimalTracking) console.warn(` + PRIVACY NOTICES + ------------------------------- + Telemetry is current set to full and the following information are sent to the server anonymously: + * Discord Bot's number of guilds & users + * Current Source Version + * NodeJS Version + * OS Version + * CPU version, name, core count, architecture, and model + * Current Process up-time + * System total ram and freed ram + * Client name and id + * Guild ID + ------------------------------- + If you wish to minimize the information that are being sent, please set "minimalTracking" to true in the config + `); + else console.warn(` + PRIVACY NOTICES + ------------------------------- + Minimal tracking has been enabled; the following information are sent anonymously: + * Current Source Version + * NodeJS Version + ------------------------------- + `); + connect(); deployCmd.deployCommands(); } }; From 96518a2e37c1a0fa38a370e95d9c17d52f993347 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Fri, 23 Jun 2023 18:23:30 -0400 Subject: [PATCH 32/52] Cleaned up eslint warnings --- src/Types.ts | 4 +++- src/utils/logs.ts | 2 +- src/utils/pgsqlDriver.ts | 15 ++++++++++----- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Types.ts b/src/Types.ts index c88eb7c1..2f907157 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -144,13 +144,15 @@ export type locale = { export type command = { data: SlashCommandBuilder; + // eslint-disable-next-line no-unused-vars execute: (interaction: Interaction, client: DiscordClient) => Promise | void; } export interface DiscordClient extends Client { config: config; - db: QuickDB; + db: QuickDB; locales: locale; + // eslint-disable-next-line no-unused-vars msToHm: (ms: number | Date) => string; commands: Collection; } diff --git a/src/utils/logs.ts b/src/utils/logs.ts index aa38a733..5e5c74a1 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -49,7 +49,7 @@ type log = { ticketId?: string; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars export const log = async(logs: log, client: DiscordClient) => { if (!client.config.logs) return; if (!client.config.logsChannelId) return; diff --git a/src/utils/pgsqlDriver.ts b/src/utils/pgsqlDriver.ts index 912ee5e4..2351aaf2 100644 --- a/src/utils/pgsqlDriver.ts +++ b/src/utils/pgsqlDriver.ts @@ -29,7 +29,7 @@ export class PostgresDriver { async disconnect(): Promise { this.checkConnection(); - await this.conn!.end(); + await this.conn?.end(); } private checkConnection(): void { @@ -40,13 +40,14 @@ export class PostgresDriver { async prepare(table: string): Promise { this.checkConnection(); - await this.conn!.query( + await this.conn?.query( `CREATE TABLE IF NOT EXISTS ${table} (id VARCHAR(255), value TEXT)` ); } - + // eslint-disable-next-line @typescript-eslint/no-explicit-any async getAllRows(table: string): Promise<{ id: string; value: any }[]> { this.checkConnection(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const queryResult = await this.conn!.query(`SELECT * FROM ${table}`); return queryResult.rows.map((row) => ({ id: row.id, @@ -59,6 +60,7 @@ export class PostgresDriver { key: string ): Promise<[T | null, boolean]> { this.checkConnection(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const queryResult = await this.conn!.query( `SELECT value FROM ${table} WHERE id = $1`, [key] @@ -71,6 +73,7 @@ export class PostgresDriver { async setRowByKey( table: string, key: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any value: any, update: boolean ): Promise { @@ -79,12 +82,12 @@ export class PostgresDriver { const stringifiedValue = JSON.stringify(value); if (update) { - await this.conn!.query( + await this.conn?.query( `UPDATE ${table} SET value = $1 WHERE id = $2`, [stringifiedValue, key] ); } else { - await this.conn!.query( + await this.conn?.query( `INSERT INTO ${table} (id, value) VALUES ($1, $2)`, [key, stringifiedValue] ); @@ -95,12 +98,14 @@ export class PostgresDriver { async deleteAllRows(table: string): Promise { this.checkConnection(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const queryResult = await this.conn!.query(`DELETE FROM ${table}`); return queryResult.rowCount; } async deleteRowByKey(table: string, key: string): Promise { this.checkConnection(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const queryResult = await this.conn!.query( `DELETE FROM ${table} WHERE id = $1`, [key] From a914e8edd2852c6f93601ccfc7a757db35715401 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Fri, 23 Jun 2023 18:40:42 -0400 Subject: [PATCH 33/52] Cleaned up some extra license message --- src/events/ready.ts | 33 +-------------------------------- src/utils/close.ts | 33 --------------------------------- src/utils/createTicket.ts | 29 ----------------------------- 3 files changed, 1 insertion(+), 94 deletions(-) diff --git a/src/events/ready.ts b/src/events/ready.ts index 861837b3..5d5c24e8 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -60,44 +60,13 @@ export default { const embed = client.locales.embeds.openTicket; - /* - Copyright 2023 Sayrix (github.com/Sayrix) - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - embed.color = parseInt(client.config.mainColor, 16); + // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) const footer = embed.footer.text.replace("ticket.pm", ""); embed.footer.text = `ticket.pm ${footer.trim() !== "" ? `- ${footer}` : ""}`; // Please respect the LICENSE :D // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) - /* - Copyright 2023 Sayrix (github.com/Sayrix) - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - const row = new ActionRowBuilder().addComponents( new ButtonBuilder().setCustomId("openTicket").setLabel(client.locales.other.openTicketButtonMSG).setStyle(ButtonStyle.Primary) ); diff --git a/src/utils/close.ts b/src/utils/close.ts index 6862cd80..7b5669c3 100644 --- a/src/utils/close.ts +++ b/src/utils/close.ts @@ -155,23 +155,6 @@ export async function close(interaction: ButtonInteraction | CommandInteraction .replace("REASON", ticket.closeReason) .replace("CLOSERNAME", interaction.user.tag) ) - - /* - Copyright 2023 Sayrix (github.com/Sayrix) - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - .setFooter({ // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) text: `ticket.pm ${footer.trim() !== "" ? `- ${footer}` : ""}`, // Please respect the LICENSE :D @@ -179,22 +162,6 @@ export async function close(interaction: ButtonInteraction | CommandInteraction iconURL: lEmbed.ticketClosedDM.footer.iconUrl }); - /* - Copyright 2023 Sayrix (github.com/Sayrix) - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - client.users.fetch(creator).then((user) => { user .send({ diff --git a/src/utils/createTicket.ts b/src/utils/createTicket.ts index 3141fae6..dcefa78d 100644 --- a/src/utils/createTicket.ts +++ b/src/utils/createTicket.ts @@ -154,42 +154,13 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo .replace("REASON8", reason[7]) .replace("REASON9", reason[8]) ) - /* - Copyright 2023 Sayrix (github.com/Sayrix) - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ .setFooter({ // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) text: `ticket.pm ${footer.trim() !== "" ? `- ${footer}` : ""}`, // Please respect the LICENSE :D // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) iconURL: lEmbeds.ticketOpened.footer.iconUrl }); - /* - Copyright 2023 Sayrix (github.com/Sayrix) - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ const row = new ActionRowBuilder(); if (client.config.closeButton) { From 016b1b4c0ad5051d55618b09b5a77a68e5de0a5c Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Fri, 23 Jun 2023 20:10:00 -0400 Subject: [PATCH 34/52] Fixed embed color bugs --- config/config.example.jsonc | 2 +- src/Types.ts | 10 +++++----- src/events/ready.ts | 21 ++++++++++++--------- src/utils/close.ts | 2 +- src/utils/createTicket.ts | 3 ++- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/config/config.example.jsonc b/config/config.example.jsonc index a41b03fe..378059e8 100644 --- a/config/config.example.jsonc +++ b/config/config.example.jsonc @@ -1,7 +1,7 @@ { "clientId": "1111111111111111111", // The id of the discord bot "guildId": "1111111111111111111", // The id of the discord server - "mainColor": "f6c42f", // The hex color of the embeds by default + "mainColor": "#f6c42f", // The hex color of the embeds by default "lang": "main", // If you want to set english please set "main" /* diff --git a/src/Types.ts b/src/Types.ts index 2f907157..7ac7545a 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -1,6 +1,6 @@ /* Licensed under Apache License 2.0: https://github.com/Sayrix/Ticket-Bot/blob/typescript/LICENSE */ -import type { Client, Collection, Interaction, SlashCommandBuilder } from "discord.js"; +import type { Client, Collection, ColorResolvable, Interaction, SlashCommandBuilder } from "discord.js"; import type { QuickDB } from "quick.db"; // Config types and setups @@ -15,7 +15,7 @@ export type TicketType = { name: string; description: string; emoji: string; - color: string; + color?: ColorResolvable; categoryId: string; ticketNameOption: string; customDescription: string; @@ -34,7 +34,7 @@ type dbConn = { export type config = { clientId: string; guildId: string; - mainColor: string; + mainColor: ColorResolvable; lang: string; // Tho can be cs/de/es/fr/main/tr type but we can't guarantee what users put // Database credentials are deprecated, will be removed when Prisma are in-place. postgre?: dbConn; @@ -69,7 +69,7 @@ export type locale = { embeds: { openTicket: { title: string, - color?: number, + color?: ColorResolvable, description: string, footer: { text: string @@ -89,7 +89,7 @@ export type locale = { }, ticketClosedDM: { title: string, - color?: string, + color?: ColorResolvable, description: string, footer: { text: string, diff --git a/src/events/ready.ts b/src/events/ready.ts index 5d5c24e8..8b1aa101 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -3,7 +3,7 @@ import readline from "readline"; import axios from "axios"; import {client as WebSocketClient, connection} from "websocket"; import { DiscordClient, SayrixSponsorData } from "../Types"; -import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js"; import os from "os"; import deployCmd from "../deploy-commands"; @@ -58,14 +58,17 @@ export default { process.exit(0); } - const embed = client.locales.embeds.openTicket; - - embed.color = parseInt(client.config.mainColor, 16); - - // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) - const footer = embed.footer.text.replace("ticket.pm", ""); - embed.footer.text = `ticket.pm ${footer.trim() !== "" ? `- ${footer}` : ""}`; // Please respect the LICENSE :D - // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) + const embedDat = client.locales.embeds.openTicket; + const footer = embedDat.footer.text.replace("ticket.pm", ""); + const embed = new EmbedBuilder() + .setTitle(embedDat.title) + .setColor(embedDat.color ?? client.config.mainColor) + .setDescription(embedDat.description) + // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) + .setFooter({ + text: `ticket.pm ${footer.trim() !== "" ? `- ${footer}` : ""}` // Please respect the LICENSE :D + }); + // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) const row = new ActionRowBuilder().addComponents( new ButtonBuilder().setCustomId("openTicket").setLabel(client.locales.other.openTicketButtonMSG).setStyle(ButtonStyle.Primary) diff --git a/src/utils/close.ts b/src/utils/close.ts index 7b5669c3..e9e998b6 100644 --- a/src/utils/close.ts +++ b/src/utils/close.ts @@ -147,7 +147,7 @@ export async function close(interaction: ButtonInteraction | CommandInteraction const footer = lEmbed.ticketClosedDM.footer.text.replace("ticket.pm", ""); const tiketClosedDMEmbed = new EmbedBuilder() - .setColor(`#${lEmbed.ticketClosedDM.color ? lEmbed.ticketClosedDM.color : client.config.mainColor}`) + .setColor(lEmbed.ticketClosedDM.color ?? client.config.mainColor) .setDescription( client.locales.embeds.ticketClosedDM.description .replace("TICKETCOUNT", ticket.id) diff --git a/src/utils/createTicket.ts b/src/utils/createTicket.ts index dcefa78d..85fca5c9 100644 --- a/src/utils/createTicket.ts +++ b/src/utils/createTicket.ts @@ -120,8 +120,9 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo } const lEmbeds = client.locales.embeds; const footer = lEmbeds.ticketOpened.footer.text.replace("ticket.pm", ""); + if(ticketType.color?.toString().trim() === "") ticketType.color = undefined; const ticketOpenedEmbed = new EmbedBuilder() - .setColor(`#${ticketType.color ? ticketType.color : client.config.mainColor}`) + .setColor(ticketType.color ?? client.config.mainColor) .setTitle(lEmbeds.ticketOpened.title.replace("CATEGORYNAME", ticketType.name)) .setDescription( ticketType.customDescription From a10e4f641eb8660e257197cc89654258f5363af7 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Sat, 24 Jun 2023 21:21:54 -0400 Subject: [PATCH 35/52] removed quick.db module --- package.json | 7 +-- src/utils/pgsqlDriver.ts | 115 --------------------------------------- 2 files changed, 2 insertions(+), 120 deletions(-) delete mode 100644 src/utils/pgsqlDriver.ts diff --git a/package.json b/package.json index 9a0e7282..e0ee1dd2 100644 --- a/package.json +++ b/package.json @@ -34,11 +34,7 @@ "fs-extra": "^11.1.1", "jsonc": "^2.0.0", "mongoose": "^7.3.0", - "mysql2": "^3.3.5", - "pg": "^8.11.0", - "quick.db": "^9.1.6", "readline": "^1.3.0", - "rimraf": "^5.0.1", "ticket-bot-transcript-uploader": "^1.3.0", "websocket": "^1.0.34" }, @@ -57,6 +53,7 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^4.2.1", "prettier": "^2.8.8", - "typescript": "^5.1.3" + "typescript": "^5.1.3", + "rimraf": "^5.0.1" } } diff --git a/src/utils/pgsqlDriver.ts b/src/utils/pgsqlDriver.ts deleted file mode 100644 index 2351aaf2..00000000 --- a/src/utils/pgsqlDriver.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Official pre-released quick.db Postgresql driver. Remove this file when the driver is officially released ~ zhiyan114 - * Why add this? PostgreSQL is a better database than MySQL. Alternative would be to use Prisma, - * but I don't want to esentially re-write the database logic and cause confusions. - * Source: https://github.com/plexidev/quick.db/blob/dev/src/drivers/PostgresDriver.ts - * LICENSE: https://github.com/plexidev/quick.db/blob/dev/LICENSE.md - */ - -import { Client, ClientConfig } from "pg"; - -export class PostgresDriver { - private static instance: PostgresDriver; - private config: ClientConfig; - private conn: Client | undefined; - - constructor(config: ClientConfig) { - this.config = config; - } - - static createSingleton(config: ClientConfig): PostgresDriver { - if (!this.instance) this.instance = new PostgresDriver(config); - return this.instance; - } - - async connect(): Promise { - this.conn = new Client(this.config); - await this.conn.connect(); - } - - async disconnect(): Promise { - this.checkConnection(); - await this.conn?.end(); - } - - private checkConnection(): void { - if (!this.conn) { - throw new Error("No connection to postgres database"); - } - } - - async prepare(table: string): Promise { - this.checkConnection(); - await this.conn?.query( - `CREATE TABLE IF NOT EXISTS ${table} (id VARCHAR(255), value TEXT)` - ); - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - async getAllRows(table: string): Promise<{ id: string; value: any }[]> { - this.checkConnection(); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const queryResult = await this.conn!.query(`SELECT * FROM ${table}`); - return queryResult.rows.map((row) => ({ - id: row.id, - value: JSON.parse(row.value), - })); - } - - async getRowByKey( - table: string, - key: string - ): Promise<[T | null, boolean]> { - this.checkConnection(); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const queryResult = await this.conn!.query( - `SELECT value FROM ${table} WHERE id = $1`, - [key] - ); - - if (queryResult.rowCount < 1) return [null, false]; - return [JSON.parse(queryResult.rows[0].value), true]; - } - - async setRowByKey( - table: string, - key: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value: any, - update: boolean - ): Promise { - this.checkConnection(); - - const stringifiedValue = JSON.stringify(value); - - if (update) { - await this.conn?.query( - `UPDATE ${table} SET value = $1 WHERE id = $2`, - [stringifiedValue, key] - ); - } else { - await this.conn?.query( - `INSERT INTO ${table} (id, value) VALUES ($1, $2)`, - [key, stringifiedValue] - ); - } - - return value; - } - - async deleteAllRows(table: string): Promise { - this.checkConnection(); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const queryResult = await this.conn!.query(`DELETE FROM ${table}`); - return queryResult.rowCount; - } - - async deleteRowByKey(table: string, key: string): Promise { - this.checkConnection(); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const queryResult = await this.conn!.query( - `DELETE FROM ${table} WHERE id = $1`, - [key] - ); - return queryResult.rowCount; - } -} \ No newline at end of file From ae311c452f9adce293ccf0b56f5f5f3b47512a62 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Sat, 24 Jun 2023 22:29:47 -0400 Subject: [PATCH 36/52] feat: Added prisma sql and its associated sql --- .env.example | 3 ++- .gitignore | 3 ++- package.json | 9 +++++--- prisma/compatible.sql | 40 +++++++++++++++++++++++++++++++++ prisma/postgre.sql | 52 +++++++++++++++++++++++++++++++++++++++++++ prisma/schema.prisma | 27 ++++++++++++++++++++++ 6 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 prisma/compatible.sql create mode 100644 prisma/postgre.sql create mode 100644 prisma/schema.prisma diff --git a/.env.example b/.env.example index 8f36cad0..7cb14b2f 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,5 @@ # Your discord token TOKEN="" # Prisma Database URL (refer to docs for more details) -DATABASE_URL="postgresql://user:password@localhost:5432/database" \ No newline at end of file +# Refer to https://www.prisma.io/docs/concepts/database-connectors to use other databases +DATABASE_URL="file:./tixbot.db" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 054fc02f..2ba41346 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ package-lock.json config.jsonc token.json dist -.env \ No newline at end of file +.env +*.db \ No newline at end of file diff --git a/package.json b/package.json index e0ee1dd2..cbd07221 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,9 @@ "description": "Bot with a ticket system using Discord.js v14", "main": "dist/index.js", "scripts": { - "start": "node dist/index.js", + "setup": "npm install && prisma db push", "build": "rimraf dist && tsc", + "start": "node dist/index.js", "format:fix": "prettier --write .", "format:check": "prettier --check .", "lint:fix": "eslint --fix . --ext .ts", @@ -27,6 +28,7 @@ }, "homepage": "https://github.com/Sayrix/ticket-bot#readme", "dependencies": { + "@prisma/client": "^4.16.1", "axios": "^1.4.0", "better-sqlite3": "^8.4.0", "discord.js": "^14.11.0", @@ -53,7 +55,8 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^4.2.1", "prettier": "^2.8.8", - "typescript": "^5.1.3", - "rimraf": "^5.0.1" + "prisma": "^4.16.1", + "rimraf": "^5.0.1", + "typescript": "^5.1.3" } } diff --git a/prisma/compatible.sql b/prisma/compatible.sql new file mode 100644 index 00000000..01a37db5 --- /dev/null +++ b/prisma/compatible.sql @@ -0,0 +1,40 @@ +/* +Copyright © 2023 小兽兽/zhiyan114 (github.com/zhiyan114) +File is licensed respectively under the terms of the Apache License 2.0 +or whichever license the project is using at the time https://github.com/Sayrix/Ticket-Bot/blob/orm/LICENSE + +This file is from postgre.sql but modified for sqlite and mysql compatibility with prisma. +RANT: I LOVE AND HATE SQLITE. Why can't it just support a little more features... + +For production use, please use "prisma db push" instead or follow the documentation: https://doc.ticket.pm/docs/intro. +*/ + + +/* +this will be used for +* openTicketMessageId +* ticketCount +*/ + +CREATE TABLE IF NOT EXISTS config ( + key TEXT PRIMARY KEY, + value TEXT +); + +/* +this will be used for storing tickets +*/ + +CREATE TABLE IF NOT EXISTS tickets ( + id SERIAL PRIMARY KEY, + messageid TEXT NOT NULL UNIQUE, + categorycode TEXT NOT NULL, + invited TEXT NOT NULL DEFAULT '[]', + reason TEXT NOT NULL, + creator TEXT NOT NULL, + createdat INTEGER NOT NULL, + claimedby TEXT, + claimedat INTEGER, + closereason TEXT, + transcript TEXT +); diff --git a/prisma/postgre.sql b/prisma/postgre.sql new file mode 100644 index 00000000..4ee26b73 --- /dev/null +++ b/prisma/postgre.sql @@ -0,0 +1,52 @@ +/* +Copyright © 2023 小兽兽/zhiyan114 (github.com/zhiyan114) +File is licensed respectively under the terms of the Apache License 2.0 +or whichever license the project is using at the time https://github.com/Sayrix/Ticket-Bot/blob/orm/LICENSE + +For production use, please don't try to use this file, even if you're using postgresql, +since the code is tailored towards compatibility.sql. You will break the software if you do so. +You have been warned. + +I wrote this in-case multi-db support will eventually be dropped, and I'm a big postgresql fan ^w^ +*/ + + + +/* +this will be used for +* openTicketMessageId +* ticketCount +*/ + +CREATE TABLE IF NOT EXISTS config ( + key VARCHAR(256) PRIMARY KEY, + value TEXT +); + +/* +this will be used for storing tickets +*/ + +CREATE TABLE IF NOT EXISTS tickets ( + id SERIAL PRIMARY KEY, + messageid TEXT NOT NULL UNIQUE, + categorycode TEXT NOT NULL, + reason TEXT NOT NULL, + creator TEXT NOT NULL, + createdat TIMESTAMP NOT NULL DEFAULT NOW(), + claimedby TEXT, + claimedat TIMESTAMP, + closereason TEXT, + transcript TEXT +); + +/* +this will be used to handle ticket invites +*/ + +CREATE TABLE IF NOT EXISTS invites ( + id SERIAL PRIMARY KEY, + ticketid TEXT NOT NULL, + userid TEXT NOT NULL, + CONSTRAINT FK_ticketID FOREIGN KEY(ticketid) REFERENCES tickets(messageid) ON DELETE CASCADE +); diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 00000000..d06cb532 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,27 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "sqlite" + url = env("DATABASE_URL") +} + +model config { + key String @id + value String? +} + +model tickets { + id Int @id @default(autoincrement()) + messageid String @unique + categorycode String + invited String @default("[]") + reason String + creator String + createdat Int + claimedby String? + claimedat Int? + closereason String? + transcript String? +} From 53e4c6b8308a2b639577cd31a2634f48214aed50 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Sat, 24 Jun 2023 22:36:10 -0400 Subject: [PATCH 37/52] Renamed some column names --- prisma/compatible.sql | 2 +- prisma/postgre.sql | 2 +- prisma/schema.prisma | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/prisma/compatible.sql b/prisma/compatible.sql index 01a37db5..08fc1a8b 100644 --- a/prisma/compatible.sql +++ b/prisma/compatible.sql @@ -27,7 +27,7 @@ this will be used for storing tickets CREATE TABLE IF NOT EXISTS tickets ( id SERIAL PRIMARY KEY, - messageid TEXT NOT NULL UNIQUE, + channelid TEXT NOT NULL UNIQUE, categorycode TEXT NOT NULL, invited TEXT NOT NULL DEFAULT '[]', reason TEXT NOT NULL, diff --git a/prisma/postgre.sql b/prisma/postgre.sql index 4ee26b73..e19d924f 100644 --- a/prisma/postgre.sql +++ b/prisma/postgre.sql @@ -29,7 +29,7 @@ this will be used for storing tickets CREATE TABLE IF NOT EXISTS tickets ( id SERIAL PRIMARY KEY, - messageid TEXT NOT NULL UNIQUE, + channelid TEXT NOT NULL UNIQUE, categorycode TEXT NOT NULL, reason TEXT NOT NULL, creator TEXT NOT NULL, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d06cb532..d7912a5c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -14,7 +14,7 @@ model config { model tickets { id Int @id @default(autoincrement()) - messageid String @unique + channelid String @unique categorycode String invited String @default("[]") reason String From f713744885bc223f625bfcfe38579164bfe3ecc2 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Sat, 24 Jun 2023 22:45:14 -0400 Subject: [PATCH 38/52] feat: Converted index and commands to support prisma --- src/Types.ts | 4 +-- src/commands/add.ts | 29 ++++++++++++++++---- src/commands/remove.ts | 20 +++++++++----- src/commands/rename.ts | 6 ++++- src/index.ts | 61 ++---------------------------------------- 5 files changed, 47 insertions(+), 73 deletions(-) diff --git a/src/Types.ts b/src/Types.ts index 7ac7545a..91f199fb 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -1,7 +1,7 @@ /* Licensed under Apache License 2.0: https://github.com/Sayrix/Ticket-Bot/blob/typescript/LICENSE */ +import { PrismaClient } from "@prisma/client"; import type { Client, Collection, ColorResolvable, Interaction, SlashCommandBuilder } from "discord.js"; -import type { QuickDB } from "quick.db"; // Config types and setups type TicketQuestion = { @@ -150,7 +150,7 @@ export type command = { export interface DiscordClient extends Client { config: config; - db: QuickDB; + prisma: PrismaClient; locales: locale; // eslint-disable-next-line no-unused-vars msToHm: (ms: number | Date) => string; diff --git a/src/commands/add.ts b/src/commands/add.ts index 62bc3923..7ccff876 100644 --- a/src/commands/add.ts +++ b/src/commands/add.ts @@ -25,14 +25,33 @@ export default { .addUserOption((input) => input.setName("user").setDescription("The user to add").setRequired(true)), async execute(interaction: CommandInteraction, client: DiscordClient) { const added = interaction.options.getUser("user", true); - const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); + + const ticket = await client.prisma.tickets.findUnique({ + select: { + id: true, + invited: true, + }, + where: { + channelid: interaction.channel?.id + } + }); if (!ticket) return interaction.reply({ content: "Ticket not found", ephemeral: true }).catch((e) => console.log(e)); - if (ticket.invited.includes(added.id)) return interaction.reply({ content: "User already added", ephemeral: true }).catch((e) => console.log(e)); + + const invited = JSON.parse(ticket.invited) as string[]; + if (invited.includes(added.id)) return interaction.reply({ content: "User already added", ephemeral: true }).catch((e) => console.log(e)); - if (ticket.invited.lenght >= 25) + if (invited.length >= 25) return interaction.reply({ content: "You can't add more than 25 users", ephemeral: true }).catch((e) => console.log(e)); - client.db.push(`tickets_${interaction.channel?.id}.invited`, added.id); + invited.push(added.id); + await client.prisma.tickets.update({ + data: { + invited: JSON.stringify(invited) + }, + where: { + channelid: interaction.channel?.id + } + }); await (interaction.channel as TextChannel | null)?.permissionOverwrites .edit(added, { @@ -50,7 +69,7 @@ export default { { LogType: "userAdded", user: interaction.user, - ticketId: ticket.id, + ticketId: ticket.id.toString(), ticketChannelId: interaction.channel?.id, target: added, }, diff --git a/src/commands/remove.ts b/src/commands/remove.ts index cc9da471..7a946aab 100644 --- a/src/commands/remove.ts +++ b/src/commands/remove.ts @@ -21,15 +21,23 @@ export default{ data: new SlashCommandBuilder().setName("remove").setDescription("Remove someone from the ticket"), async execute(interaction: CommandInteraction, client: DiscordClient) { - const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); + const ticket = await client.prisma.tickets.findUnique({ + select: { + invited: true, + }, + where: { + channelid: interaction.channel?.id + } + }); if (!ticket) return interaction.reply({ content: "Ticket not found", ephemeral: true }).catch((e) => console.log(e)); - if (ticket.invited.length < 1) return interaction.reply({ content: "There are no users to remove", ephemeral: true }).catch((e) => console.log(e)); + + const invited = JSON.parse(ticket.invited) as string[]; + if (invited.length < 1) return interaction.reply({ content: "There are no users to remove", ephemeral: true }).catch((e) => console.log(e)); - for (let i = 0; i < ticket.invited.length; i++) { - await client.users.fetch(ticket.invited[i]); + const addedUsers: User[] = []; + for (let i = 0; i < invited.length; i++) { + addedUsers.push(await client.users.fetch(invited[i])); } - // @TODO: Fix type definitions when I figure it out via ORM migration. For now assign a random type that gets the error removed. - const addedUsers: User[] = ticket.invited.map((user: string) => client.users.cache.get(user)); const row = new ActionRowBuilder().addComponents( new StringSelectMenuBuilder() diff --git a/src/commands/rename.ts b/src/commands/rename.ts index e096f49d..2c6e7368 100644 --- a/src/commands/rename.ts +++ b/src/commands/rename.ts @@ -29,7 +29,11 @@ export default { * @returns */ async execute(interaction: CommandInteraction, client: DiscordClient) { - const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); + const ticket = await client.prisma.tickets.findUnique({ + where: { + channelid: interaction.channel?.id + } + }); if (!ticket) return interaction.reply({ content: "Ticket not found", ephemeral: true }).catch((e) => console.log(e)); if (!(interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id))) return interaction diff --git a/src/index.ts b/src/index.ts index 6cf574f0..83c2d25b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,11 +18,10 @@ import { Interaction } from "discord.js"; import fs from "fs-extra"; import path from "node:path"; import { Client, Collection, GatewayIntentBits } from "discord.js"; -import {QuickDB, MySQLDriver } from "quick.db"; import { jsonc } from "jsonc"; import { DiscordClient, config, locale } from "./Types"; -import {PostgresDriver} from "./utils/pgsqlDriver"; import { config as envconf } from "dotenv"; +import { PrismaClient } from "@prisma/client"; // Initalize .env file as environment try {envconf();} @@ -76,64 +75,8 @@ const client = new Client({ // All variables stored in the client object client.config = config; +client.prisma = new PrismaClient(); -let db: QuickDB | undefined; - -if (client.config.postgre?.enabled) { - // PostgreSQL Support. - (async () => { - try { - require.resolve("pg"); - } catch (e) { - console.error("pg driver is not installed!\n\nPlease run \"npm i pg\" in the console!"); - throw e; - } - const pgsql = new PostgresDriver({ - host: client.config.postgre?.host, - user: client.config.postgre?.user, - password: client.config.postgre?.password, - database: client.config.postgre?.database - }); - - await pgsql.connect(); - - db = new QuickDB({ - driver: pgsql, - table: client.config.postgre?.table ?? "json" - }); - client.db = db; - })(); -} else if (client.config.mysql?.enabled) { - // MySQL Support - (async () => { - try { - require.resolve("mysql2"); - } catch (e) { - console.error("mysql2 is not installed!\n\nPlease run \"npm i mysql2\" in the console!"); - throw e; - } - - const mysql = new MySQLDriver({ - host: client.config.mysql?.host, - user: client.config.mysql?.user, - password: client.config.mysql?.password, - database: client.config.mysql?.database, - charset: "utf8mb4" - }); - - await mysql.connect(); - - db = new QuickDB({ - driver: mysql, - table: client.config.mysql?.table ?? "json" - }); - client.db = db; - })(); -} else { - // SQLite Support - db = new QuickDB(); - client.db = db; -} // eslint-disable-next-line @typescript-eslint/no-var-requires client.locales = require(`../locales/${client.config.lang}.json`) as locale; client.msToHm = function dhm(ms: number | Date) { From 29f5b1a79fc06e007ffd164a713f6491f019e069 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Sat, 24 Jun 2023 23:45:43 -0400 Subject: [PATCH 39/52] feat(events): Converted to Prisma system --- src/events/interactionCreate.ts | 23 ++++++++++++++++------- src/events/ready.ts | 15 ++++++++++++--- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 4aa40f7a..1483da54 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -47,8 +47,9 @@ export default { } } - const all = (await client.db.all()).filter((data) => data.id.startsWith("tickets_")); - const ticketsOpened = all.filter((data) => data.value.creator === interaction.user.id && data.value.closed === false).length; + const ticketsOpened = (await client.prisma.$queryRaw<{count:number}> + `SELECT COUNT(*) as count FROM tickets WHERE closereason IS NOT NULL`) + .count; if (client.config.maxTicketOpened !== 0) { // If maxTicketOpened is 0, it means that there is no limit if (ticketsOpened > client.config.maxTicketOpened || ticketsOpened === client.config.maxTicketOpened) { @@ -126,8 +127,10 @@ export default { if (interaction.isStringSelectMenu()) { if (interaction.customId === "selectTicketType") { - const all = (await client.db.all()).filter((data) => data.id.startsWith("tickets_")); - const ticketsOpened = all.filter((data) => data.value.creator === interaction.user.id && data.value.closed === false).length; + const ticketsOpened = (await client.prisma.$queryRaw<{count:number}> + `SELECT COUNT(*) as count FROM tickets WHERE closereason IS NOT NULL`) + .count; + if (client.config.maxTicketOpened !== 0) { // If maxTicketOpened is 0, it means that there is no limit if (ticketsOpened > client.config.maxTicketOpened || ticketsOpened === client.config.maxTicketOpened) { @@ -164,8 +167,14 @@ export default { } if (interaction.customId === "removeUser") { - const ticket = await client.db.get(`tickets_${interaction.message.channelId}`); - client.db.pull(`tickets_${interaction.message.channel.id}.invited`, interaction.values); + const ticket = await client.prisma.tickets.findUnique({ + select: { + id: true, + }, + where: { + channelid: interaction.message.channelId + } + }); interaction.values.forEach((value) => { (interaction.channel as GuildChannel | null)?.permissionOverwrites.delete(value).catch((e) => console.log(e)); @@ -174,7 +183,7 @@ export default { { LogType: "userRemoved", user: interaction.user, - ticketId: ticket.id, + ticketId: ticket?.id.toString(), ticketChannelId: interaction.channel?.id, target: { id: value, diff --git a/src/events/ready.ts b/src/events/ready.ts index 8b1aa101..8a1a0208 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -42,7 +42,11 @@ export default { process.exit(0); } - const embedMessageId = await client.db.get("temp.openTicketMessageId"); + const embedMessageId = await client.prisma.config.findUnique({ + where: { + key: "openTicketMessageId", + } + }); await client.channels.fetch(client.config.openTicketChannelId).catch(() => { console.error("The channel to open tickets is not found!"); process.exit(0); @@ -75,7 +79,7 @@ export default { ); try { - const msg = await openTicketChannel?.messages?.fetch(embedMessageId).catch((ex) => console.error(ex)); + const msg = embedMessageId?.value ? await openTicketChannel?.messages?.fetch(embedMessageId.value).catch((ex) => console.error(ex)) : undefined; if (msg && msg.id) { msg.edit({ embeds: [embed], @@ -88,7 +92,12 @@ export default { embeds: [embed], components: [row] }).then((rMsg) => { - client.db.set("temp.openTicketMessageId", rMsg.id); + client.prisma.config.create({ + data: { + key: "openTicketMessageId", + value: rMsg.id + } + }); }); } } catch (e) { From 337ef9c7ca9c113e774b89a9e5b9736ca42d50b6 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Sat, 24 Jun 2023 23:57:13 -0400 Subject: [PATCH 40/52] Added missing tickets column --- prisma/compatible.sql | 1 + prisma/postgre.sql | 1 + prisma/schema.prisma | 1 + 3 files changed, 3 insertions(+) diff --git a/prisma/compatible.sql b/prisma/compatible.sql index 08fc1a8b..52b869e1 100644 --- a/prisma/compatible.sql +++ b/prisma/compatible.sql @@ -28,6 +28,7 @@ this will be used for storing tickets CREATE TABLE IF NOT EXISTS tickets ( id SERIAL PRIMARY KEY, channelid TEXT NOT NULL UNIQUE, + messageid TEXT NOT NULL UNIQUE, categorycode TEXT NOT NULL, invited TEXT NOT NULL DEFAULT '[]', reason TEXT NOT NULL, diff --git a/prisma/postgre.sql b/prisma/postgre.sql index e19d924f..5bae99bf 100644 --- a/prisma/postgre.sql +++ b/prisma/postgre.sql @@ -30,6 +30,7 @@ this will be used for storing tickets CREATE TABLE IF NOT EXISTS tickets ( id SERIAL PRIMARY KEY, channelid TEXT NOT NULL UNIQUE, + messageid TEXT NOT NULL UNIQUE, categorycode TEXT NOT NULL, reason TEXT NOT NULL, creator TEXT NOT NULL, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d7912a5c..d234746b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -15,6 +15,7 @@ model config { model tickets { id Int @id @default(autoincrement()) channelid String @unique + messageid String @unique categorycode String invited String @default("[]") reason String From 9237b67fa26062cf6123a975d152662334a3ce5d Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Sun, 25 Jun 2023 00:21:31 -0400 Subject: [PATCH 41/52] Added missing 'closed' column --- prisma/compatible.sql | 2 ++ prisma/postgre.sql | 2 ++ prisma/schema.prisma | 2 ++ 3 files changed, 6 insertions(+) diff --git a/prisma/compatible.sql b/prisma/compatible.sql index 52b869e1..2b7baf2d 100644 --- a/prisma/compatible.sql +++ b/prisma/compatible.sql @@ -36,6 +36,8 @@ CREATE TABLE IF NOT EXISTS tickets ( createdat INTEGER NOT NULL, claimedby TEXT, claimedat INTEGER, + closedby TEXT, + closedat INTEGER, closereason TEXT, transcript TEXT ); diff --git a/prisma/postgre.sql b/prisma/postgre.sql index 5bae99bf..9b539512 100644 --- a/prisma/postgre.sql +++ b/prisma/postgre.sql @@ -37,6 +37,8 @@ CREATE TABLE IF NOT EXISTS tickets ( createdat TIMESTAMP NOT NULL DEFAULT NOW(), claimedby TEXT, claimedat TIMESTAMP, + closedby TEXT, + closedat INTEGER, closereason TEXT, transcript TEXT ); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d234746b..b5f93579 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -23,6 +23,8 @@ model tickets { createdat Int claimedby String? claimedat Int? + closedby String? + closedat Int? closereason String? transcript String? } From c17dc54d1ecf7f1c57509b900ac78b7b7e24a90b Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Sun, 25 Jun 2023 01:01:34 -0400 Subject: [PATCH 42/52] Converted all code to use prisma #159 --- prisma/postgre.sql | 2 +- src/events/ready.ts | 6 +-- src/utils/claim.ts | 30 +++++++++----- src/utils/close.ts | 83 +++++++++++++++++++++------------------ src/utils/createTicket.ts | 41 ++++++++++--------- src/utils/delete.ts | 11 ++++-- src/utils/logs.ts | 6 +-- 7 files changed, 100 insertions(+), 79 deletions(-) diff --git a/prisma/postgre.sql b/prisma/postgre.sql index 9b539512..b99634a8 100644 --- a/prisma/postgre.sql +++ b/prisma/postgre.sql @@ -4,7 +4,7 @@ File is licensed respectively under the terms of the Apache License 2.0 or whichever license the project is using at the time https://github.com/Sayrix/Ticket-Bot/blob/orm/LICENSE For production use, please don't try to use this file, even if you're using postgresql, -since the code is tailored towards compatibility.sql. You will break the software if you do so. +Since the code is tailored towards compatibility.sql, it will break. You have been warned. I wrote this in-case multi-db support will eventually be dropped, and I'm a big postgresql fan ^w^ diff --git a/src/events/ready.ts b/src/events/ready.ts index 8a1a0208..fd8780f7 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -42,11 +42,11 @@ export default { process.exit(0); } - const embedMessageId = await client.prisma.config.findUnique({ + const embedMessageId = (await client.prisma.config.findUnique({ where: { key: "openTicketMessageId", } - }); + }))?.value; await client.channels.fetch(client.config.openTicketChannelId).catch(() => { console.error("The channel to open tickets is not found!"); process.exit(0); @@ -79,7 +79,7 @@ export default { ); try { - const msg = embedMessageId?.value ? await openTicketChannel?.messages?.fetch(embedMessageId.value).catch((ex) => console.error(ex)) : undefined; + const msg = embedMessageId ? await openTicketChannel?.messages?.fetch(embedMessageId).catch((ex) => console.error(ex)) : undefined; if (msg && msg.id) { msg.edit({ embeds: [embed], diff --git a/src/utils/claim.ts b/src/utils/claim.ts index 6163575d..8e9ce293 100644 --- a/src/utils/claim.ts +++ b/src/utils/claim.ts @@ -24,7 +24,13 @@ import { log } from "./logs"; * @param {Discord.Client} client */ export const claim = async(interaction: ButtonInteraction | CommandInteraction, client: DiscordClient) => { - const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); + const ticket = await client.prisma.tickets.findUnique({ + where: { + channelid: interaction.channel?.id + } + }); + const claimed = ticket?.claimedat && ticket.claimedby; + if (!ticket) return interaction.reply({ content: "Ticket not found", @@ -41,7 +47,7 @@ export const claim = async(interaction: ButtonInteraction | CommandInteraction, }) .catch((e) => console.log(e)); - if (ticket.claimed) + if (claimed) return interaction .reply({ content: client.locales.ticketAlreadyClaimed, @@ -53,20 +59,24 @@ export const claim = async(interaction: ButtonInteraction | CommandInteraction, { LogType: "ticketClaim", user: interaction.user, - ticketId: ticket.id, + ticketId: ticket.id.toString(), ticketChannelId: interaction.channel?.id, - ticketCreatedAt: ticket.createdAt, + ticketCreatedAt: ticket.createdat, }, client ); - await client.db.set(`tickets_${interaction.channel?.id}.claimed`, true); - await client.db.set(`tickets_${interaction.channel?.id}.claimedBy`, interaction.user.id); - await client.db.set(`tickets_${interaction.channel?.id}.claimedAt`, Date.now()); + client.prisma.tickets.update({ + data: { + claimedby: interaction.user.id, + claimedat: Date.now() + }, + where: { + channelid: interaction.channel?.id, + } + }); - //await interaction.channel?.messages.fetch(); // Commented bc it seems useless - const messageId = await client.db.get(`tickets_${interaction.channel?.id}.messageId`); - const msg = interaction.channel?.messages.cache.get(messageId); + const msg = await interaction.channel?.messages.fetch(ticket.messageid); const oldEmbed = msg?.embeds[0].data; const newEmbed = new EmbedBuilder(oldEmbed) .setDescription(oldEmbed?.description + `\n\n ${client.locales.other.claimedBy.replace("USER", `<@${interaction.user.id}>`)}`); diff --git a/src/utils/close.ts b/src/utils/close.ts index e9e998b6..c1cea0a9 100644 --- a/src/utils/close.ts +++ b/src/utils/close.ts @@ -24,7 +24,12 @@ limitations under the License. export async function close(interaction: ButtonInteraction | CommandInteraction | ModalSubmitInteraction, client: DiscordClient, reason?: string) { if (!client.config.createTranscript) domain = client.locales.other.unavailable; - const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); + const ticket = await client.prisma.tickets.findUnique({ + where: { + channelid: interaction.channel?.id + } + }); + const ticketClosed = ticket?.closedat && ticket.closedby; if (!ticket) return interaction.editReply({ content: "Ticket not found" }).catch((e) => console.log(e)); if ( @@ -37,7 +42,7 @@ export async function close(interaction: ButtonInteraction | CommandInteraction }) .catch((e) => console.log(e)); - if (ticket.closed) + if (ticketClosed) return interaction .editReply({ content: client.locales.ticketAlreadyClosed @@ -50,31 +55,23 @@ export async function close(interaction: ButtonInteraction | CommandInteraction user: interaction.user, ticketId: ticket.id, ticketChannelId: interaction.channel?.id, - ticketCreatedAt: ticket.createdAt, + ticketCreatedAt: ticket.createdat, reason: reason }, client ); + + // Normally the user that closes the ticket will get posted here, but we'll do it when the ticket finalizes - await client.db.set(`tickets_${interaction.channel?.id}.closedBy`, interaction.user.id); - await client.db.set(`tickets_${interaction.channel?.id}.closedAt`, Date.now()); - - if (reason) { - await client.db.set(`tickets_${interaction.channel?.id}.closeReason`, reason); - } else { - await client.db.set(`tickets_${interaction.channel?.id}.closeReason`, client.locales.other.noReasonGiven); - } - - const creator = await client.db.get(`tickets_${interaction.channel?.id}.creator`); - const invited = await client.db.get(`tickets_${interaction.channel?.id}.invited`); + const creator = ticket.creator; + const invited = JSON.parse(ticket.invited) as string[]; (interaction.channel as TextChannel | null)?.permissionOverwrites .edit(creator, { ViewChannel: false }) .catch((e: unknown) => console.log(e)); - // TODO: Replace user: string with the proper type once ORM is implemented - invited.forEach(async (user: string) => { + invited.forEach(async (user) => { (interaction.channel as TextChannel | null)?.permissionOverwrites .edit(user, { ViewChannel: false @@ -88,13 +85,19 @@ export async function close(interaction: ButtonInteraction | CommandInteraction }) .catch((e) => console.log(e)); - await interaction.channel?.messages.fetch(); - - async function close(id: string) { + async function _close(id: string) { if (client.config.closeTicketCategoryId) (interaction.channel as TextChannel | null)?.setParent(client.config.closeTicketCategoryId).catch((e) => console.log(e)); - const messageId = await client.db.get(`tickets_${interaction.channel?.id}.messageId`); - const msg = interaction.channel?.messages.cache.get(messageId); + // We can guarantee this is not null because it's already checked above. + // Should re-write this to prevent nested functions :/ + let ticket = (await client.prisma.tickets.findUnique({ + where: { + channelid: interaction.channel?.id + } + })); + if(!ticket) return console.error("close.ts: UNEXPECTED ERROR - _close func encountered null ticket"); + + const msg = await interaction.channel?.messages.fetch(ticket.messageid); const embed = new EmbedBuilder(msg?.embeds[0].data); const rowAction = new ActionRowBuilder(); @@ -113,20 +116,24 @@ export async function close(interaction: ButtonInteraction | CommandInteraction }) .catch((e) => console.log(e)); - await client.db.set(`tickets_${interaction.channel?.id}.closed`, true); - interaction.channel?.send({ content: client.locales.ticketTranscriptCreated.replace( "TRANSCRIPTURL", domain === client.locales.other.unavailable ? client.locales.other.unavailable : `<${domain}${id}>` ) - }) - .catch((e) => console.log(e)); - await client.db.set( - `tickets_${interaction.channel?.id}.transcriptURL`, - domain === client.locales.other.unavailable ? client.locales.other.unavailable : `${domain}${id}` - ); - const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); + }).catch((e) => console.log(e)); + + ticket = await client.prisma.tickets.update({ + data: { + closedby: interaction.user.id, + closedat: Date.now(), + closereason: reason, + transcript: domain === client.locales.other.unavailable ? client.locales.other.unavailable : `${domain}${id}` + }, + where: { + channelid: interaction.channel?.id + } + }); const row = new ActionRowBuilder().addComponents( new ButtonBuilder().setCustomId("deleteTicket").setLabel(client.locales.other.deleteTicketButtonMSG).setStyle(ButtonStyle.Danger) @@ -136,8 +143,8 @@ export async function close(interaction: ButtonInteraction | CommandInteraction embeds: [ JSON.parse( JSON.stringify(lEmbed.ticketClosed) - .replace("TICKETCOUNT", ticket.id) - .replace("REASON", ticket.closeReason.replace(/[\n\r]/g, "\\n")) + .replace("TICKETCOUNT", ticket.id.toString()) + .replace("REASON", (ticket.closereason ?? client.locales.other.noReasonGiven).replace(/[\n\r]/g, "\\n")) .replace("CLOSERNAME", interaction.user.tag) ) ], @@ -146,13 +153,13 @@ export async function close(interaction: ButtonInteraction | CommandInteraction .catch((e) => console.log(e)); const footer = lEmbed.ticketClosedDM.footer.text.replace("ticket.pm", ""); - const tiketClosedDMEmbed = new EmbedBuilder() + const ticketClosedDMEmbed = new EmbedBuilder() .setColor(lEmbed.ticketClosedDM.color ?? client.config.mainColor) .setDescription( client.locales.embeds.ticketClosedDM.description - .replace("TICKETCOUNT", ticket.id) + .replace("TICKETCOUNT", ticket.id.toString()) .replace("TRANSCRIPTURL", `[\`${domain}${id}\`](${domain}${id})`) - .replace("REASON", ticket.closeReason) + .replace("REASON", ticket.closereason ?? client.locales.other.noReasonGiven) .replace("CLOSERNAME", interaction.user.tag) ) .setFooter({ @@ -165,14 +172,14 @@ export async function close(interaction: ButtonInteraction | CommandInteraction client.users.fetch(creator).then((user) => { user .send({ - embeds: [tiketClosedDMEmbed] + embeds: [ticketClosedDMEmbed] }) .catch((e) => console.log(e)); }); } if (!client.config.createTranscript) { - close(""); + _close(""); return; } @@ -213,7 +220,7 @@ export async function close(interaction: ButtonInteraction | CommandInteraction } }) .catch(console.error); - close(ts?.data); + _close(ts?.data); } }); } diff --git a/src/utils/createTicket.ts b/src/utils/createTicket.ts index 85fca5c9..b7882774 100644 --- a/src/utils/createTicket.ts +++ b/src/utils/createTicket.ts @@ -43,16 +43,20 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo let ticketName = ""; + let ticketCount = (await client.prisma.$queryRaw<{count:number}> + `SELECT COUNT(*) as count FROM tickets`) + .count; + if (ticketType.ticketNameOption) { ticketName = ticketType.ticketNameOption .replace("USERNAME", interaction.user.username) .replace("USERID", interaction.user.id) - .replace("TICKETCOUNT", (await client.db.get("temp.ticketCount")) ?? "0"); + .replace("TICKETCOUNT", ticketCount.toString() ?? "0"); } else { ticketName = client.config.ticketNameOption .replace("USERNAME", interaction.user.username) .replace("USERID", interaction.user.id) - .replace("TICKETCOUNT", (await client.db.get("temp.ticketCount")) ?? "0"); + .replace("TICKETCOUNT", ticketCount.toString() ?? "0"); } if(!interaction.guild) return console.error("Interaction createTicket was not executed in a guild"); @@ -78,22 +82,8 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo client ); - await client.db.add("temp.ticketCount", 1); - const ticketId = await client.db.get("temp.ticketCount"); - await client.db.set(`tickets_${channel.id}`, { - id: ticketId - 1, - category: ticketType, - reason: allReasons, - creator: interaction.user.id, - invited: [], - createdAt: Date.now(), - claimed: false, - claimedBy: null, - claimedAt: null, - closed: false, - closedBy: null, - closedAt: null, - }); + // Client.db is set here and incremented ticket count + ticketCount++; channel.permissionOverwrites .edit(interaction.user, { @@ -130,7 +120,7 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo .replace("CATEGORYNAME", ticketType.name) .replace("USERNAME", interaction.user.username) .replace("USERID", interaction.user.id) - .replace("TICKETCOUNT", (await client.db.get("temp.ticketCount")) || "0") + .replace("TICKETCOUNT", ticketCount.toString() || "0") .replace("REASON1", reason[0]) .replace("REASON2", reason[1]) .replace("REASON3", reason[2]) @@ -144,7 +134,7 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo .replace("CATEGORYNAME", ticketType.name) .replace("USERNAME", interaction.user.username) .replace("USERID", interaction.user.id) - .replace("TICKETCOUNT", (await client.db.get("temp.ticketCount")) || "0") + .replace("TICKETCOUNT", ticketCount.toString() || "0") .replace("REASON1", reason[0]) .replace("REASON2", reason[1]) .replace("REASON3", reason[2]) @@ -207,7 +197,16 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo channel .send(body) .then((msg) => { - client.db.set(`tickets_${channel.id}.messageId`, msg.id); + client.prisma.tickets.create({ + data: { + categorycode: ticketType.codeName, + reason: allReasons, + creator: interaction.user.id, + createdat: Date.now(), + channelid: channel.id, + messageid: msg.id + } + }); msg.pin().then(() => { msg.channel.bulkDelete(1); }); diff --git a/src/utils/delete.ts b/src/utils/delete.ts index 090ca751..fc38cf9f 100644 --- a/src/utils/delete.ts +++ b/src/utils/delete.ts @@ -19,15 +19,20 @@ import { DiscordClient } from "../Types"; import { log } from "./logs"; export const deleteTicket = async (interaction: ButtonInteraction, client: DiscordClient) => { - const ticket = await client.db.get(`tickets_${interaction.channel?.id}`); + const ticket = await client.prisma.tickets.findUnique({ + where: { + channelid: interaction.channel?.id + } + }); + if (!ticket) return interaction.reply({ content: "Ticket not found", ephemeral: true }).catch((e) => console.log(e)); log( { LogType: "ticketDelete", user: interaction.user, ticketId: ticket.id, - ticketCreatedAt: ticket.createdAt, - transcriptURL: ticket.transcriptURL, + ticketCreatedAt: ticket.createdat, + transcriptURL: ticket.transcript ?? undefined, }, client ); diff --git a/src/utils/logs.ts b/src/utils/logs.ts index 5e5c74a1..a70381ec 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -26,14 +26,14 @@ type log = { LogType: "ticketClaim" | "ticketClose" user: User ticketChannelId?: string; - ticketId?: string; + ticketId?: string | number; reason?: string; ticketCreatedAt: number; } | { LogType: "ticketDelete" user: User ticketChannelId?: string; - ticketId?: string; + ticketId?: string | number; reason?: string; ticketCreatedAt: number; transcriptURL?: string; @@ -46,7 +46,7 @@ type log = { }; ticketChannelId?: string; reason?: string; - ticketId?: string; + ticketId?: string | number; } // eslint-disable-next-line @typescript-eslint/no-unused-vars From e0c8cfedef703482bce8158a64da9aba25c29ebe Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Sun, 25 Jun 2023 01:37:13 -0400 Subject: [PATCH 43/52] Fixed bug preventing prisma from creating data --- prisma/compatible.sql | 6 +++--- prisma/postgre.sql | 2 +- prisma/schema.prisma | 6 +++--- src/events/interactionCreate.ts | 8 ++++---- src/events/ready.ts | 2 +- src/utils/createTicket.ts | 7 +++---- src/utils/logs.ts | 10 +++++----- 7 files changed, 20 insertions(+), 21 deletions(-) diff --git a/prisma/compatible.sql b/prisma/compatible.sql index 2b7baf2d..0af07869 100644 --- a/prisma/compatible.sql +++ b/prisma/compatible.sql @@ -33,11 +33,11 @@ CREATE TABLE IF NOT EXISTS tickets ( invited TEXT NOT NULL DEFAULT '[]', reason TEXT NOT NULL, creator TEXT NOT NULL, - createdat INTEGER NOT NULL, + createdat BIGINT NOT NULL, claimedby TEXT, - claimedat INTEGER, + claimedat BIGINT, closedby TEXT, - closedat INTEGER, + closedat BIGINT, closereason TEXT, transcript TEXT ); diff --git a/prisma/postgre.sql b/prisma/postgre.sql index b99634a8..db3f86ce 100644 --- a/prisma/postgre.sql +++ b/prisma/postgre.sql @@ -38,7 +38,7 @@ CREATE TABLE IF NOT EXISTS tickets ( claimedby TEXT, claimedat TIMESTAMP, closedby TEXT, - closedat INTEGER, + closedat TIMESTAMP, closereason TEXT, transcript TEXT ); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b5f93579..ee9fdb33 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -20,11 +20,11 @@ model tickets { invited String @default("[]") reason String creator String - createdat Int + createdat BigInt claimedby String? - claimedat Int? + claimedat BigInt? closedby String? - closedat Int? + closedat BigInt? closereason String? transcript String? } diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 1483da54..c57c132a 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -47,12 +47,12 @@ export default { } } - const ticketsOpened = (await client.prisma.$queryRaw<{count:number}> - `SELECT COUNT(*) as count FROM tickets WHERE closereason IS NOT NULL`) - .count; + const ticketsOpened = (await client.prisma.$queryRaw<[{count: bigint}]> + `SELECT COUNT(*) as count FROM tickets`)[0].count; + if (client.config.maxTicketOpened !== 0) { // If maxTicketOpened is 0, it means that there is no limit - if (ticketsOpened > client.config.maxTicketOpened || ticketsOpened === client.config.maxTicketOpened) { + if (ticketsOpened > client.config.maxTicketOpened || ticketsOpened === BigInt(client.config.maxTicketOpened)) { return interaction .editReply({ content: client.locales.ticketLimitReached.replace("TICKETLIMIT", client.config.maxTicketOpened.toString()) diff --git a/src/events/ready.ts b/src/events/ready.ts index fd8780f7..4279f0cb 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -97,7 +97,7 @@ export default { key: "openTicketMessageId", value: rMsg.id } - }); + }).then(); // I need .then() for it to execute?!?!?? }); } } catch (e) { diff --git a/src/utils/createTicket.ts b/src/utils/createTicket.ts index b7882774..94496703 100644 --- a/src/utils/createTicket.ts +++ b/src/utils/createTicket.ts @@ -43,9 +43,8 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo let ticketName = ""; - let ticketCount = (await client.prisma.$queryRaw<{count:number}> - `SELECT COUNT(*) as count FROM tickets`) - .count; + let ticketCount = (await client.prisma.$queryRaw<[{count: bigint}]> + `SELECT COUNT(*) as count FROM tickets`)[0].count; if (ticketType.ticketNameOption) { ticketName = ticketType.ticketNameOption @@ -206,7 +205,7 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo channelid: channel.id, messageid: msg.id } - }); + }).then(); // Again why tf do I need .then()?!?!? msg.pin().then(() => { msg.channel.bulkDelete(1); }); diff --git a/src/utils/logs.ts b/src/utils/logs.ts index a70381ec..f9f82d2d 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -28,14 +28,14 @@ type log = { ticketChannelId?: string; ticketId?: string | number; reason?: string; - ticketCreatedAt: number; + ticketCreatedAt: number | bigint; } | { LogType: "ticketDelete" user: User ticketChannelId?: string; ticketId?: string | number; reason?: string; - ticketCreatedAt: number; + ticketCreatedAt: number | bigint; transcriptURL?: string; } | { @@ -85,7 +85,7 @@ export const log = async(logs: log, client: DiscordClient) => { .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) .setDescription( `${logs.user.tag} (<@${logs.user.id}>) Claimed the ticket n°${logs.ticketId} (<#${logs.ticketChannelId}>) after ${client.msToHm( - new Date(Date.now() - logs.ticketCreatedAt) + new Date(Number(BigInt(Date.now()) - BigInt(logs.ticketCreatedAt))) )} of creation` ); webhook @@ -104,7 +104,7 @@ export const log = async(logs: log, client: DiscordClient) => { .setDescription( `${logs.user.tag} (<@${logs.user.id}>) Closed the ticket n°${logs.ticketId} (<#${logs.ticketChannelId}>) with the reason: \`${ logs.reason - }\` after ${client.msToHm(new Date(Date.now() - logs.ticketCreatedAt))} of creation` + }\` after ${client.msToHm(Number(BigInt(Date.now()) - BigInt(logs.ticketCreatedAt)))} of creation` ); webhook @@ -122,7 +122,7 @@ export const log = async(logs: log, client: DiscordClient) => { .setAuthor({ name: logs.user.tag, iconURL: logs.user.displayAvatarURL() }) .setDescription( `${logs.user.tag} (<@${logs.user.id}>) Deleted the ticket n°${logs.ticketId} after ${client.msToHm( - new Date(Date.now() - logs.ticketCreatedAt) + new Date(Number(BigInt(Date.now()) - BigInt(logs.ticketCreatedAt))) )} of creation\n\nTranscript: ${logs.transcriptURL}` ); From cbb4df47415e4fdaf4eb89ccba4c97513bbcee15 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Sun, 25 Jun 2023 01:39:35 -0400 Subject: [PATCH 44/52] prisma save category as raw JSON string for now --- prisma/compatible.sql | 2 +- prisma/postgre.sql | 2 +- prisma/schema.prisma | 2 +- src/utils/createTicket.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/prisma/compatible.sql b/prisma/compatible.sql index 0af07869..0ae354d2 100644 --- a/prisma/compatible.sql +++ b/prisma/compatible.sql @@ -29,7 +29,7 @@ CREATE TABLE IF NOT EXISTS tickets ( id SERIAL PRIMARY KEY, channelid TEXT NOT NULL UNIQUE, messageid TEXT NOT NULL UNIQUE, - categorycode TEXT NOT NULL, + category TEXT NOT NULL, invited TEXT NOT NULL DEFAULT '[]', reason TEXT NOT NULL, creator TEXT NOT NULL, diff --git a/prisma/postgre.sql b/prisma/postgre.sql index db3f86ce..dcd975d6 100644 --- a/prisma/postgre.sql +++ b/prisma/postgre.sql @@ -31,7 +31,7 @@ CREATE TABLE IF NOT EXISTS tickets ( id SERIAL PRIMARY KEY, channelid TEXT NOT NULL UNIQUE, messageid TEXT NOT NULL UNIQUE, - categorycode TEXT NOT NULL, + category JSON NOT NULL, reason TEXT NOT NULL, creator TEXT NOT NULL, createdat TIMESTAMP NOT NULL DEFAULT NOW(), diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ee9fdb33..a15fffbe 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -16,7 +16,7 @@ model tickets { id Int @id @default(autoincrement()) channelid String @unique messageid String @unique - categorycode String + category String invited String @default("[]") reason String creator String diff --git a/src/utils/createTicket.ts b/src/utils/createTicket.ts index 94496703..5f5cca42 100644 --- a/src/utils/createTicket.ts +++ b/src/utils/createTicket.ts @@ -198,7 +198,7 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo .then((msg) => { client.prisma.tickets.create({ data: { - categorycode: ticketType.codeName, + category: JSON.stringify(ticketType), reason: allReasons, creator: interaction.user.id, createdat: Date.now(), From 1d6c93129e3bb64b775214517e2c7ca9c61a58aa Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Sun, 25 Jun 2023 13:05:53 -0400 Subject: [PATCH 45/52] Fixed claim info not registered to db --- src/utils/claim.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/claim.ts b/src/utils/claim.ts index 8e9ce293..c71a805d 100644 --- a/src/utils/claim.ts +++ b/src/utils/claim.ts @@ -66,7 +66,7 @@ export const claim = async(interaction: ButtonInteraction | CommandInteraction, client ); - client.prisma.tickets.update({ + await client.prisma.tickets.update({ data: { claimedby: interaction.user.id, claimedat: Date.now() From 83968d6b0474ea26df830bcc3d0fe961b9ab57c2 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Sun, 25 Jun 2023 13:16:38 -0400 Subject: [PATCH 46/52] Updated README to reflect new version --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index fad5f218..4b657835 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,12 @@ Ticket Bot is a open source project of an ticket discord bot using [discord.js]( The documentation is available [here](https://doc.ticket.pm/) +## ⚠️ Incompatibility +This new source code you're seeing are completely refactored and will be incompatible with the older version. +I recommend you finish up all of your remaining support ticket and start migrating to the newer version. +If you prefer to stay in the older version, you can download anything that is less than `3.0.0` from the release +or clone from the `old` branch (i.e. `git clone -b old https://github.com/Sayrix/Ticket-Bot.git`) + ## 💬 Discord You can come on the discord: https://discord.gg/VasYV6MEJy From c8af008f9917315d9c60a5d12f591f71779bec47 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Sun, 25 Jun 2023 13:58:49 -0400 Subject: [PATCH 47/52] chores: Increment package.json version to 3.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cbd07221..fdf8a7c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ticket-bot", - "version": "2.5.0", + "version": "3.0.0", "description": "Bot with a ticket system using Discord.js v14", "main": "dist/index.js", "scripts": { From 8b203e379f8aac52db5a1e6ec3ecaaa3c6f11cae Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Sun, 25 Jun 2023 13:59:59 -0400 Subject: [PATCH 48/52] fixed invalid LICENSE url in *.sql file --- prisma/compatible.sql | 2 +- prisma/postgre.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prisma/compatible.sql b/prisma/compatible.sql index 0ae354d2..7b562102 100644 --- a/prisma/compatible.sql +++ b/prisma/compatible.sql @@ -1,7 +1,7 @@ /* Copyright © 2023 小兽兽/zhiyan114 (github.com/zhiyan114) File is licensed respectively under the terms of the Apache License 2.0 -or whichever license the project is using at the time https://github.com/Sayrix/Ticket-Bot/blob/orm/LICENSE +or whichever license the project is using at the time https://github.com/Sayrix/Ticket-Bot/blob/main/LICENSE This file is from postgre.sql but modified for sqlite and mysql compatibility with prisma. RANT: I LOVE AND HATE SQLITE. Why can't it just support a little more features... diff --git a/prisma/postgre.sql b/prisma/postgre.sql index dcd975d6..b051f03e 100644 --- a/prisma/postgre.sql +++ b/prisma/postgre.sql @@ -1,7 +1,7 @@ /* Copyright © 2023 小兽兽/zhiyan114 (github.com/zhiyan114) File is licensed respectively under the terms of the Apache License 2.0 -or whichever license the project is using at the time https://github.com/Sayrix/Ticket-Bot/blob/orm/LICENSE +or whichever license the project is using at the time https://github.com/Sayrix/Ticket-Bot/blob/main/LICENSE For production use, please don't try to use this file, even if you're using postgresql, Since the code is tailored towards compatibility.sql, it will break. From 7a8e91ef6867d4e14b5caba7c1b953d930d10d45 Mon Sep 17 00:00:00 2001 From: Sayrix <43046854+Sayrix@users.noreply.github.com> Date: Sun, 25 Jun 2023 20:18:35 +0200 Subject: [PATCH 49/52] telemetry warning only on first startup and added emojis on update check --- src/deploy-commands.ts | 7 +++-- src/events/ready.ts | 65 +++++++++++++++++++++++++----------------- src/index.ts | 6 ++-- 3 files changed, 46 insertions(+), 32 deletions(-) diff --git a/src/deploy-commands.ts b/src/deploy-commands.ts index b54b3c97..66eb3a59 100644 --- a/src/deploy-commands.ts +++ b/src/deploy-commands.ts @@ -3,17 +3,18 @@ import path from "node:path"; import { jsonc } from "jsonc"; import { REST } from "@discordjs/rest"; import { Routes } from "discord.js"; +import { DiscordClient } from "./Types"; export default { /** * @param {Discord.Client} client */ - async deployCommands() { + async deployCommands(client: DiscordClient) { const commands = []; const commandsPath = path.join(__dirname, "commands"); const commandFiles = fs.readdirSync(commandsPath).filter((file) => file.endsWith(".js")); - const { clientId, guildId } = jsonc.parse(fs.readFileSync(path.join(__dirname, "../config/config.jsonc"), "utf8")); + const { guildId } = jsonc.parse(fs.readFileSync(path.join(__dirname, "../config/config.jsonc"), "utf8")); for (const file of commandFiles) { const filePath = path.join(commandsPath, file); @@ -25,7 +26,7 @@ export default { const rest = new REST({ version: "10" }).setToken(process.env["TOKEN"]); rest - .put(Routes.applicationGuildCommands(clientId, guildId), { body: commands }) + .put(Routes.applicationGuildCommands(client.user?.id ?? "", guildId), { body: commands }) .then(() => console.log("✅ Successfully registered application commands.")) .catch(console.error); }, diff --git a/src/events/ready.ts b/src/events/ready.ts index 4279f0cb..b998f35e 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -146,7 +146,6 @@ export default { process.stdout.write( `\x1b[0m🚀 The bot is ready! Logged in as \x1b[37;46;1m${client.user?.tag}\x1b[0m (\x1b[37;46;1m${client.user?.id}\x1b[0m) \x1b[0m🌟 You can leave a star on GitHub: \x1b[37;46;1mhttps://github.com/Sayrix/ticket-bot \x1b[0m - \x1b[0m📖 Documentation: \x1b[37;46;1mhttps://doc.ticket.pm \x1b[0m \x1b[0m⛅ Host your ticket-bot by being a sponsor from 1$/month: \x1b[37;46;1mhttps://github.com/sponsors/Sayrix \x1b[0m\n`.replace(/\t/g, "") ); @@ -241,32 +240,46 @@ export default { ws.connect("wss://ws.ticket.pm", "echo-protocol"); } - if(!client.config.minimalTracking) console.warn(` - PRIVACY NOTICES - ------------------------------- - Telemetry is current set to full and the following information are sent to the server anonymously: - * Discord Bot's number of guilds & users - * Current Source Version - * NodeJS Version - * OS Version - * CPU version, name, core count, architecture, and model - * Current Process up-time - * System total ram and freed ram - * Client name and id - * Guild ID - ------------------------------- - If you wish to minimize the information that are being sent, please set "minimalTracking" to true in the config - `); - else console.warn(` - PRIVACY NOTICES - ------------------------------- - Minimal tracking has been enabled; the following information are sent anonymously: - * Current Source Version - * NodeJS Version - ------------------------------- - `); + + if ((await client.prisma.config.findUnique({ + where: { + key: "firstStart", + } + })) === null) { + await client.prisma.config.create({ + data: { + key: "firstStart", + value: "true", + } + }); + + if(!client.config.minimalTracking) console.warn(` + PRIVACY NOTICES + ------------------------------- + Telemetry is current set to full and the following information are sent to the server anonymously: + * Discord Bot's number of guilds & users + * Current Source Version + * NodeJS Version + * OS Version + * CPU version, name, core count, architecture, and model + * Current Process up-time + * System total ram and freed ram + * Client name and id + * Guild ID + ------------------------------- + If you wish to minimize the information that are being sent, please set "minimalTracking" to true in the config + `.replace(/\t/g, "")); + else console.warn(` + PRIVACY NOTICES + ------------------------------- + Minimal tracking has been enabled; the following information are sent anonymously: + * Current Source Version + * NodeJS Version + ------------------------------- + `.replace(/\t/g, "")); + } connect(); - deployCmd.deployCommands(); + deployCmd.deployCommands(client); } }; diff --git a/src/index.ts b/src/index.ts index 83c2d25b..57fbed33 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,7 +46,7 @@ Connecting to Discord... // Update Detector fetch("https://api.github.com/repos/Sayrix/Ticket-Bot/tags").then((res) => { - if (Math.floor(res.status / 100) !== 2) return console.warn("[Version Check] Failed to pull latest version from server"); + if (Math.floor(res.status / 100) !== 2) return console.warn("🔄 Failed to pull latest version from server"); res.json().then((json) => { // Assumign the format stays consistent (i.e. x.x.x) const latest = json[0].name.split(".").map((k: string) => parseInt(k)); @@ -58,8 +58,8 @@ fetch("https://api.github.com/repos/Sayrix/Ticket-Bot/tags").then((res) => { (latest[0] === current[0] && latest[1] > current[1]) || (latest[0] === current[0] && latest[1] === current[1] && latest[2] > current[2]) ) - console.warn(`[Version Check] New version available: ${json[0].name}; Current Version: ${current.join(".")}`); - else console.log("[Version Check] Up to date"); + console.warn(`🔄 New version available: ${json[0].name}; Current Version: ${current.join(".")}`); + else console.log("🔄 The ticket-bot is up to date"); }); }); From 478f57eceb27ea4bf14690100413225c7bcdfd15 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Sun, 25 Jun 2023 14:31:36 -0400 Subject: [PATCH 50/52] Removed database conn from config --- config/config.example.jsonc | 22 ---------------------- src/Types.ts | 11 ----------- 2 files changed, 33 deletions(-) diff --git a/config/config.example.jsonc b/config/config.example.jsonc index 378059e8..b586fa1e 100644 --- a/config/config.example.jsonc +++ b/config/config.example.jsonc @@ -4,28 +4,6 @@ "mainColor": "#f6c42f", // The hex color of the embeds by default "lang": "main", // If you want to set english please set "main" - /* - Pick a database driver, postgres will take priority over mysql (if both are enabled, which please don't do). - If neither option is enabled, SQLite will be used. - *PostgreSQL will be using default schema* - */ - "postgre": { - "enabled": false, - "host": "postgresql.example.com", // The host of the PostgreSQL database - "user": "postgres", // The user of the PostgreSQL database - "password": "password", // The password of the PostgreSQL database - "database": "postgres", // The name of the PostgreSQL database - "table": "json" // The name of the table where the tickets will be saved - }, - "mysql": { - "enabled": false, - "host": "mysql.example.com", // The host of the MySQL database - "user": "mysql", // The user of the MySQL database - "password": "password", // The password of the MySQL database - "database": "ticketbot", // The name of the MySQL database - "table": "json" // The name of the table where the tickets will be saved - }, - "closeTicketCategoryId": "", // The id of the category where a closed ticket will be moved to. Leave blank to disable this feature "openTicketChannelId": "1111111111111111111", // The id of the channel where the message to create a ticket will be sent diff --git a/src/Types.ts b/src/Types.ts index 91f199fb..6010566d 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -23,22 +23,11 @@ export type TicketType = { askQuestions: boolean; questions: TicketQuestion[]; } -type dbConn = { - enabled: boolean; - host: string; - user: string; - password: string; - database: string; - table: string; -} export type config = { clientId: string; guildId: string; mainColor: ColorResolvable; lang: string; // Tho can be cs/de/es/fr/main/tr type but we can't guarantee what users put - // Database credentials are deprecated, will be removed when Prisma are in-place. - postgre?: dbConn; - mysql?: dbConn; closeTicketCategoryId: string; openTicketChannelId: string; ticketTypes: TicketType[]; From c87476a607de0c493b8e2b2f14835ff0440d0849 Mon Sep 17 00:00:00 2001 From: Sayrix <43046854+Sayrix@users.noreply.github.com> Date: Sun, 25 Jun 2023 21:02:22 +0200 Subject: [PATCH 51/52] Added the possibility of having a uuid or emoji --- config/config.example.jsonc | 1 + src/Types.ts | 1 + src/utils/close.ts | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/config/config.example.jsonc b/config/config.example.jsonc index 378059e8..3e6e7098 100644 --- a/config/config.example.jsonc +++ b/config/config.example.jsonc @@ -99,6 +99,7 @@ "askReasonWhenClosing": true, // If false the ticket will be closed without asking the reason "createTranscript": true, // If set to true, when the ticket is closed a transcript will be generated and sent in the logs channel + "uuidType": "uuid", // uuid or emoji "status": { "enabled": true, // If you want to enable the status of the bot diff --git a/src/Types.ts b/src/Types.ts index 91f199fb..c1f2825a 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -55,6 +55,7 @@ export type config = { closeButton: boolean; askReasonWhenClosing: boolean; createTranscript: boolean; + uuidType: string, status: { enabled: boolean; text: string; diff --git a/src/utils/close.ts b/src/utils/close.ts index c1cea0a9..15827f6a 100644 --- a/src/utils/close.ts +++ b/src/utils/close.ts @@ -214,7 +214,7 @@ export async function close(interaction: ButtonInteraction | CommandInteraction console.error(err); } else { const ts = await axios - .post(`${domain}upload?key=${premiumKey}`, JSON.stringify(compressed), { + .post(`${domain}upload?key=${premiumKey}&uuid=${client.config.uuidType}`, JSON.stringify(compressed), { headers: { "Content-Type": "application/json" } From c7da65ea481496782840cf7bc107be712c03591a Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Sun, 25 Jun 2023 15:11:43 -0400 Subject: [PATCH 52/52] More relaxed embed creation policy through locales --- src/events/ready.ts | 19 +++++++++---------- src/utils/close.ts | 5 ++++- src/utils/createTicket.ts | 5 ++++- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/events/ready.ts b/src/events/ready.ts index b998f35e..d17d095c 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -62,17 +62,16 @@ export default { process.exit(0); } - const embedDat = client.locales.embeds.openTicket; + const embedDat = {...client.locales.embeds.openTicket}; const footer = embedDat.footer.text.replace("ticket.pm", ""); - const embed = new EmbedBuilder() - .setTitle(embedDat.title) - .setColor(embedDat.color ?? client.config.mainColor) - .setDescription(embedDat.description) - // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) - .setFooter({ - text: `ticket.pm ${footer.trim() !== "" ? `- ${footer}` : ""}` // Please respect the LICENSE :D - }); - // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) + // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) + embedDat.footer.text = `ticket.pm ${footer.trim() !== "" ? `- ${footer}` : ""}`; // Please respect the LICENSE :D + // Please respect the project by keeping the credits, (if it is too disturbing you can credit me in the "about me" of the bot discord) + const embed = new EmbedBuilder({ + ...embedDat, + color: 0, + }) + .setColor(embedDat.color ?? client.config.mainColor); const row = new ActionRowBuilder().addComponents( new ButtonBuilder().setCustomId("openTicket").setLabel(client.locales.other.openTicketButtonMSG).setStyle(ButtonStyle.Primary) diff --git a/src/utils/close.ts b/src/utils/close.ts index c1cea0a9..e70a6ba7 100644 --- a/src/utils/close.ts +++ b/src/utils/close.ts @@ -153,7 +153,10 @@ export async function close(interaction: ButtonInteraction | CommandInteraction .catch((e) => console.log(e)); const footer = lEmbed.ticketClosedDM.footer.text.replace("ticket.pm", ""); - const ticketClosedDMEmbed = new EmbedBuilder() + const ticketClosedDMEmbed = new EmbedBuilder({ + ...lEmbed, + color: 0, + }) .setColor(lEmbed.ticketClosedDM.color ?? client.config.mainColor) .setDescription( client.locales.embeds.ticketClosedDM.description diff --git a/src/utils/createTicket.ts b/src/utils/createTicket.ts index 5f5cca42..9d465e8d 100644 --- a/src/utils/createTicket.ts +++ b/src/utils/createTicket.ts @@ -110,7 +110,10 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo const lEmbeds = client.locales.embeds; const footer = lEmbeds.ticketOpened.footer.text.replace("ticket.pm", ""); if(ticketType.color?.toString().trim() === "") ticketType.color = undefined; - const ticketOpenedEmbed = new EmbedBuilder() + const ticketOpenedEmbed = new EmbedBuilder({ + ...lEmbeds.ticketOpened, + color: 0, + }) .setColor(ticketType.color ?? client.config.mainColor) .setTitle(lEmbeds.ticketOpened.title.replace("CATEGORYNAME", ticketType.name)) .setDescription(