From 19449f5dc90c414d05fffffad1f7d03b625a251b Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Mon, 17 Jul 2023 21:19:48 -0400 Subject: [PATCH 01/12] chores: Updated dependencies --- package-lock.json | 901 +++++++++++++++++++++++++++++++++++++--------- package.json | 10 +- 2 files changed, 728 insertions(+), 183 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6b7bd722..535d8bb0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "3.1.0", "license": "Apache-2.0", "dependencies": { - "@prisma/client": "^4.16.1", + "@prisma/client": "^5.0.0", "axios": "^1.4.0", "better-sqlite3": "^8.4.0", "discord.js": "^14.11.0", @@ -27,16 +27,16 @@ "@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", + "@typescript-eslint/eslint-plugin": "^6.1.0", + "@typescript-eslint/parser": "^6.1.0", "eslint": "^8.45.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", + "eslint-plugin-prettier": "^5.0.0", "prettier": "^3.0.0", - "prisma": "^4.16.1", + "prisma": "^5.0.0", "rimraf": "^5.0.1", "typescript": "^5.1.3" } @@ -309,16 +309,36 @@ "node": ">=14" } }, + "node_modules/@pkgr/utils": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", + "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "fast-glob": "^3.3.0", + "is-glob": "^4.0.3", + "open": "^9.1.0", + "picocolors": "^1.0.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@prisma/client": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.2.tgz", - "integrity": "sha512-qCoEyxv1ZrQ4bKy39GnylE8Zq31IRmm8bNhNbZx7bF2cU5aiCCnSa93J2imF88MBjn7J9eUQneNxUQVJdl/rPQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.0.0.tgz", + "integrity": "sha512-XlO5ELNAQ7rV4cXIDJUNBEgdLwX3pjtt9Q/RHqDpGf43szpNJx2hJnggfFs7TKNx0cOFsl6KJCSfqr5duEU/bQ==", "hasInstallScript": true, "dependencies": { - "@prisma/engines-version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81" + "@prisma/engines-version": "4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584" }, "engines": { - "node": ">=14.17" + "node": ">=16.13" }, "peerDependencies": { "prisma": "*" @@ -330,16 +350,16 @@ } }, "node_modules/@prisma/engines": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.2.tgz", - "integrity": "sha512-vx1nxVvN4QeT/cepQce68deh/Turxy5Mr+4L4zClFuK1GlxN3+ivxfuv+ej/gvidWn1cE1uAhW7ALLNlYbRUAw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.0.0.tgz", + "integrity": "sha512-kyT/8fd0OpWmhAU5YnY7eP31brW1q1YrTGoblWrhQJDiN/1K+Z8S1kylcmtjqx5wsUGcP1HBWutayA/jtyt+sg==", "devOptional": true, "hasInstallScript": true }, "node_modules/@prisma/engines-version": { - "version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81.tgz", - "integrity": "sha512-q617EUWfRIDTriWADZ4YiWRZXCa/WuhNgLTVd+HqWLffjMSPzyM5uOWoauX91wvQClSKZU4pzI4JJLQ9Kl62Qg==" + "version": "4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584.tgz", + "integrity": "sha512-HHiUF6NixsldsP3JROq07TYBLEjXFKr6PdH8H4gK/XAoTmIplOJBCgrIUMrsRAnEuGyRoRLXKXWUb943+PFoKQ==" }, "node_modules/@sapphire/async-queue": { "version": "1.5.0", @@ -418,9 +438,9 @@ } }, "node_modules/@types/node": { - "version": "20.4.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.1.tgz", - "integrity": "sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg==" + "version": "20.4.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz", + "integrity": "sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==" }, "node_modules/@types/pg": { "version": "8.10.2", @@ -471,32 +491,34 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz", - "integrity": "sha512-A5l/eUAug103qtkwccSCxn8ZRwT+7RXWkFECdA4Cvl1dOlDUgTpAOfSEElZn2uSUxhdDpnCdetrf0jvU4qrL+g==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.1.0.tgz", + "integrity": "sha512-qg7Bm5TyP/I7iilGyp6DRqqkt8na00lI6HbjWZObgk3FFSzH5ypRwAHXJhJkwiRtTcfn+xYQIMOR5kJgpo6upw==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.61.0", - "@typescript-eslint/type-utils": "5.61.0", - "@typescript-eslint/utils": "5.61.0", + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.1.0", + "@typescript-eslint/type-utils": "6.1.0", + "@typescript-eslint/utils": "6.1.0", + "@typescript-eslint/visitor-keys": "6.1.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -505,25 +527,26 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.61.0.tgz", - "integrity": "sha512-yGr4Sgyh8uO6fSi9hw3jAFXNBHbCtKKFMdX2IkT3ZqpKmtAq3lHS4ixB/COFuAIJpwl9/AqF7j72ZDWYKmIfvg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.1.0.tgz", + "integrity": "sha512-hIzCPvX4vDs4qL07SYzyomamcs2/tQYXg5DtdAfj35AyJ5PIUqhsLf4YrEIFzZcND7R2E8tpQIZKayxg8/6Wbw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.61.0", - "@typescript-eslint/types": "5.61.0", - "@typescript-eslint/typescript-estree": "5.61.0", + "@typescript-eslint/scope-manager": "6.1.0", + "@typescript-eslint/types": "6.1.0", + "@typescript-eslint/typescript-estree": "6.1.0", + "@typescript-eslint/visitor-keys": "6.1.0", "debug": "^4.3.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -532,16 +555,16 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.61.0.tgz", - "integrity": "sha512-W8VoMjoSg7f7nqAROEmTt6LoBpn81AegP7uKhhW5KzYlehs8VV0ZW0fIDVbcZRcaP3aPSW+JZFua+ysQN+m/Nw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.1.0.tgz", + "integrity": "sha512-AxjgxDn27hgPpe2rQe19k0tXw84YCOsjDJ2r61cIebq1t+AIxbgiXKvD4999Wk49GVaAcdJ/d49FYel+Pp3jjw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.61.0", - "@typescript-eslint/visitor-keys": "5.61.0" + "@typescript-eslint/types": "6.1.0", + "@typescript-eslint/visitor-keys": "6.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -549,25 +572,25 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.61.0.tgz", - "integrity": "sha512-kk8u//r+oVK2Aj3ph/26XdH0pbAkC2RiSjUYhKD+PExemG4XSjpGFeyZ/QM8lBOa7O8aGOU+/yEbMJgQv/DnCg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.1.0.tgz", + "integrity": "sha512-kFXBx6QWS1ZZ5Ni89TyT1X9Ag6RXVIVhqDs0vZE/jUeWlBv/ixq2diua6G7ece6+fXw3TvNRxP77/5mOMusx2w==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.61.0", - "@typescript-eslint/utils": "5.61.0", + "@typescript-eslint/typescript-estree": "6.1.0", + "@typescript-eslint/utils": "6.1.0", "debug": "^4.3.4", - "tsutils": "^3.21.0" + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -576,12 +599,12 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.61.0.tgz", - "integrity": "sha512-ldyueo58KjngXpzloHUog/h9REmHl59G1b3a5Sng1GfBo14BkS3ZbMEb3693gnP1k//97lh7bKsp6/V/0v1veQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.1.0.tgz", + "integrity": "sha512-+Gfd5NHCpDoHDOaU/yIF3WWRI2PcBRKKpP91ZcVbL0t5tQpqYWBs3z/GGhvU+EV1D0262g9XCnyqQh19prU0JQ==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -589,21 +612,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.61.0.tgz", - "integrity": "sha512-Fud90PxONnnLZ36oR5ClJBLTLfU4pIWBmnvGwTbEa2cXIqj70AEDEmOmpkFComjBZ/037ueKrOdHuYmSFVD7Rw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.1.0.tgz", + "integrity": "sha512-nUKAPWOaP/tQjU1IQw9sOPCDavs/iU5iYLiY/6u7gxS7oKQoi4aUxXS1nrrVGTyBBaGesjkcwwHkbkiD5eBvcg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.61.0", - "@typescript-eslint/visitor-keys": "5.61.0", + "@typescript-eslint/types": "6.1.0", + "@typescript-eslint/visitor-keys": "6.1.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -616,42 +639,41 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.61.0.tgz", - "integrity": "sha512-mV6O+6VgQmVE6+xzlA91xifndPW9ElFW8vbSF0xCT/czPXVhwDewKila1jOyRwa9AE19zKnrr7Cg5S3pJVrTWQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.1.0.tgz", + "integrity": "sha512-wp652EogZlKmQoMS5hAvWqRKplXvkuOnNzZSE0PVvsKjpexd/XznRVHAtrfHFYmqaJz0DFkjlDsGYC9OXw+OhQ==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.61.0", - "@typescript-eslint/types": "5.61.0", - "@typescript-eslint/typescript-estree": "5.61.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.1.0", + "@typescript-eslint/types": "6.1.0", + "@typescript-eslint/typescript-estree": "6.1.0", + "semver": "^7.5.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.61.0.tgz", - "integrity": "sha512-50XQ5VdbWrX06mQXhy93WywSFZZGsv3EOjq+lqp6WC2t+j3mb6A9xYVdrRxafvK88vg9k9u+CT4l6D8PEatjKg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.1.0.tgz", + "integrity": "sha512-yQeh+EXhquh119Eis4k0kYhj9vmFzNpbhM3LftWQVwqVjipCkwHBQOZutcYW+JVkjtTG9k8nrZU1UoNedPDd1A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.61.0", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "6.1.0", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -811,6 +833,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", + "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -873,6 +915,15 @@ "prebuild-install": "^7.1.0" } }, + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -891,6 +942,18 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bplist-parser": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", + "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.44" + }, + "engines": { + "node": ">= 5.10.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -956,6 +1019,21 @@ "node": ">=6.14.2" } }, + "node_modules/bundle-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", + "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", + "dev": true, + "dependencies": { + "run-applescript": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -1118,6 +1196,52 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/default-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", + "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", + "dev": true, + "dependencies": { + "bundle-name": "^3.0.0", + "default-browser-id": "^3.0.0", + "execa": "^7.1.1", + "titleize": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", + "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", + "dev": true, + "dependencies": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-properties": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", @@ -1163,9 +1287,9 @@ } }, "node_modules/discord-api-types": { - "version": "0.37.48", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.48.tgz", - "integrity": "sha512-vu2NQJD7SZRjpKDC2DPNsxTz34KS53OrotA+LGRW6mcyT55Hjqu66aRrouzjYhea7tllL9I7rvWVX7bg3aT2AQ==" + "version": "0.37.49", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.49.tgz", + "integrity": "sha512-a2vvjgmE/1cPtXOuOh5zHlLe+qheu0uO625K+FyZVFomoCmpV1xNpadC48S98K30CnQ4/XJyFGnOZLXxAyBbCQ==" }, "node_modules/discord.js": { "version": "14.11.0", @@ -1243,18 +1367,19 @@ } }, "node_modules/es-abstract": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", - "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", + "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.1", "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", + "get-intrinsic": "^1.2.1", "get-symbol-description": "^1.0.0", "globalthis": "^1.0.3", "gopd": "^1.0.1", @@ -1274,14 +1399,18 @@ "object-inspect": "^1.12.3", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", + "regexp.prototype.flags": "^1.5.0", + "safe-array-concat": "^1.0.0", "safe-regex-test": "^1.0.0", "string.prototype.trim": "^1.2.7", "string.prototype.trimend": "^1.0.6", "string.prototype.trimstart": "^1.0.6", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" + "which-typed-array": "^1.1.10" }, "engines": { "node": ">= 0.4" @@ -1449,9 +1578,9 @@ } }, "node_modules/eslint-config-airbnb-base/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -1585,9 +1714,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -1614,48 +1743,43 @@ } }, "node_modules/eslint-plugin-node/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz", + "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==", "dev": true, "dependencies": { - "prettier-linter-helpers": "^1.0.0" + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.5" }, "engines": { - "node": ">=12.0.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/prettier" }, "peerDependencies": { - "eslint": ">=7.28.0", - "prettier": ">=2.0.0" + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "prettier": ">=3.0.0" }, "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, "eslint-config-prettier": { "optional": true } } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/eslint-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", @@ -1693,9 +1817,9 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.1.tgz", + "integrity": "sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -1718,9 +1842,9 @@ } }, "node_modules/espree": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz", - "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { "acorn": "^8.9.0", @@ -1776,15 +1900,6 @@ "node": ">=4.0" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -1794,6 +1909,35 @@ "node": ">=0.10.0" } }, + "node_modules/execa": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", + "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -2124,6 +2268,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -2357,6 +2513,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -2535,6 +2700,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2565,6 +2745,24 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -2638,6 +2836,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", @@ -2669,9 +2879,9 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.11.tgz", + "integrity": "sha512-l2SCJk9RflSWHQjOJJgNsV5FnE1pq/RpHnYW6ckSjTCYypv07SMbiRSCmLQD63WOv2eXaEwNsn+7kcn3csvYSw==", "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", @@ -2704,6 +2914,39 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-wsl/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2863,6 +3106,12 @@ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", "optional": true }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2904,6 +3153,18 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -2936,9 +3197,9 @@ } }, "node_modules/minipass": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.1.tgz", - "integrity": "sha512-NQ8MCKimInjVlaIqx51RKJJB7mINVkLTJbsZKmto4UAAOC/CWXES8PGaOgoBZyqoUsUA/U3DToGK7GJkkHbjJw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.2.tgz", + "integrity": "sha512-eL79dXrE1q9dBbDCLg7xfn/vl7MS4F1gvJAgjJrQli/jbQWdUttuVawphqpffoIYfRdq78LHx6GP4bU/EQ2ATA==", "dev": true, "engines": { "node": ">=16 || 14 >=14.17" @@ -3002,9 +3263,9 @@ } }, "node_modules/mongoose": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.3.2.tgz", - "integrity": "sha512-Z86m5ASwYYFyT++wPQTtuTl5Jh052w6G1IM8LxPu/6iuqxQo6nUOaEoGZfMy0ovw3Dyw3415Jue3pYXkRqPkfA==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.3.4.tgz", + "integrity": "sha512-luvv4PKFiFYaHNn5wGIRrMML3Vvoa8lkdhcLE1S/6gY9s9CUOdEu9olbDrkhvnwRQ20j1SrQFO5JEApW0xwL3w==", "dependencies": { "bson": "^5.3.0", "kareem": "2.5.1", @@ -3094,6 +3355,33 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -3175,6 +3463,39 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", + "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "dev": true, + "dependencies": { + "default-browser": "^4.0.0", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -3358,6 +3679,12 @@ "node": ">=10" } }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -3477,20 +3804,19 @@ } }, "node_modules/prisma": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.2.tgz", - "integrity": "sha512-SYCsBvDf0/7XSJyf2cHTLjLeTLVXYfqp7pG5eEVafFLeT0u/hLFz/9W196nDRGUOo1JfPatAEb+uEnTQImQC1g==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.0.0.tgz", + "integrity": "sha512-KYWk83Fhi1FH59jSpavAYTt2eoMVW9YKgu8ci0kuUnt6Dup5Qy47pcB4/TLmiPAbhGrxxSz7gsSnJcCmkyPANA==", "devOptional": true, "hasInstallScript": true, "dependencies": { - "@prisma/engines": "4.16.2" + "@prisma/engines": "5.0.0" }, "bin": { - "prisma": "build/index.js", - "prisma2": "build/index.js" + "prisma": "build/index.js" }, "engines": { - "node": ">=14.17" + "node": ">=16.13" } }, "node_modules/proxy-from-env": { @@ -3673,6 +3999,116 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/run-applescript": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", + "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/run-applescript/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/run-applescript/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/run-applescript/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-applescript/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/run-applescript/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3696,6 +4132,24 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-array-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", + "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4060,6 +4514,18 @@ "node": ">=8" } }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -4111,6 +4577,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dev": true, + "dependencies": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -4152,6 +4634,18 @@ "discord.js": "^14.8.0" } }, + "node_modules/titleize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", + "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4191,6 +4685,18 @@ "node": ">=12" } }, + "node_modules/ts-api-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", + "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-mixer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz", @@ -4222,27 +4728,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -4283,6 +4768,57 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typed-array-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", @@ -4352,6 +4888,15 @@ "node": ">= 10.0.0" } }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -4459,9 +5004,9 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.10.tgz", + "integrity": "sha512-uxoA5vLUfRPdjCuJ1h5LlYdmTLbYfums398v3WLkM+i/Wltl2/XyZpQWKbN++ck5L64SR/grOHqtXCUKmlZPNA==", "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", diff --git a/package.json b/package.json index 91ff8928..10ff9f3a 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ }, "homepage": "https://github.com/Sayrix/ticket-bot#readme", "dependencies": { - "@prisma/client": "^4.16.1", + "@prisma/client": "^5.0.0", "axios": "^1.4.0", "better-sqlite3": "^8.4.0", "discord.js": "^14.11.0", @@ -46,16 +46,16 @@ "@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", + "@typescript-eslint/eslint-plugin": "^6.1.0", + "@typescript-eslint/parser": "^6.1.0", "eslint": "^8.45.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", + "eslint-plugin-prettier": "^5.0.0", "prettier": "^3.0.0", - "prisma": "^4.16.1", + "prisma": "^5.0.0", "rimraf": "^5.0.1", "typescript": "^5.1.3" } From 3e0d62e97d2685711ff44d0bb40778f49506e9d9 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Tue, 18 Jul 2023 13:12:06 -0400 Subject: [PATCH 02/12] feat: TOKEN check --- src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/index.ts b/src/index.ts index e2eead4d..c8dc9862 100644 --- a/src/index.ts +++ b/src/index.ts @@ -75,6 +75,9 @@ const client = new ExtendedClient({ }, config); // Login the bot +const token = process.env["TOKEN"]; +if(!token || token.trim() === "") + throw new Error("TOKEN Environment Not Found"); client.login(process.env["TOKEN"]).then(null); /* From 5a10002cf20386fa1d6c8773052ec5a8bc368c9e Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Tue, 18 Jul 2023 13:36:52 -0400 Subject: [PATCH 03/12] fix: non-existance openTicket message no longer throws error and returns undefine instead --- src/events/ready.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/events/ready.ts b/src/events/ready.ts index 367119b8..70c981f5 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -2,7 +2,7 @@ import readline from "readline"; import axios from "axios"; import {client as WebSocketClient, connection} from "websocket"; -import {ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder} from "discord.js"; +import {ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder, Message} from "discord.js"; import os from "os"; import {BaseEvent, ExtendedClient, SponsorType} from "../structure"; @@ -77,7 +77,13 @@ export default class ReadyEvent extends BaseEvent { ); try { - const msg = embedMessageId ? await openTicketChannel?.messages?.fetch(embedMessageId).catch((ex) => console.error(ex)) : undefined; + // Fetch Message object and return undefined if not found + const msg = embedMessageId ? await (()=> new Promise((res)=> { + openTicketChannel?.messages?.fetch(embedMessageId) + .then(msg=>res(msg)) + .catch(()=>res(undefined)); + }))() : undefined; + if (msg && msg.id) { msg.edit({ embeds: [embed], From 73b90002852a760d6fa2548a171de99c4c5bb1e9 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Tue, 18 Jul 2023 13:42:06 -0400 Subject: [PATCH 04/12] style: eslint uses 4 space for indent instead of tab --- .eslintrc.json | 2 +- src/commands/add.ts | 124 ++++---- src/commands/claim.ts | 18 +- src/commands/close.ts | 46 +-- src/commands/index.ts | 10 +- src/commands/remove.ts | 76 ++--- src/commands/rename.ts | 50 ++-- src/events/index.ts | 4 +- src/events/interactionCreate.ts | 442 ++++++++++++++-------------- src/events/ready.ts | 492 ++++++++++++++++---------------- src/index.ts | 40 +-- src/structure/BaseCommand.ts | 10 +- src/structure/BaseEvent.ts | 10 +- src/structure/ExtendedClient.ts | 108 +++---- src/structure/index.ts | 6 +- src/utils/claim.ts | 102 +++---- src/utils/close.ts | 386 ++++++++++++------------- src/utils/close_askReason.ts | 44 +-- src/utils/createTicket.ts | 394 ++++++++++++------------- src/utils/delete.ts | 38 +-- src/utils/logs.ts | 218 +++++++------- 21 files changed, 1310 insertions(+), 1310 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 35b11843..0bda9762 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -16,7 +16,7 @@ "no-console": "off", "no-undef": "warn", "no-constant-condition": "warn", - "indent": ["error", "tab"], + "indent": ["error", 4], "semi": ["error", "always"], "quotes": [2, "double"], "semi-style": ["error", "last"], diff --git a/src/commands/add.ts b/src/commands/add.ts index 7990396e..4157394f 100644 --- a/src/commands/add.ts +++ b/src/commands/add.ts @@ -19,68 +19,68 @@ limitations under the License. */ export default class AddCommand extends BaseCommand { - public static data: SlashCommandBuilder = new SlashCommandBuilder() - .setName("add") - .setDescription("Add someone to the ticket") - .addUserOption((input) => input.setName("user").setDescription("The user to add").setRequired(true)); - constructor(client: ExtendedClient) { - super(client); - } - - async execute(interaction: CommandInteraction) { - - const added = interaction.options.getUser("user", true); - const ticket = await this.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)); - - 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 (invited.length >= 25) - return interaction.reply({ content: "You can't add more than 25 users", ephemeral: true }).catch((e) => console.log(e)); - - invited.push(added.id); - await this.client.prisma.tickets.update({ - data: { - invited: JSON.stringify(invited) - }, - where: { - channelid: interaction.channel?.id - } - }); - - await (interaction.channel as TextChannel | null)?.permissionOverwrites - .edit(added, { - SendMessages: true, - AddReactions: true, - ReadMessageHistory: true, - AttachFiles: true, - ViewChannel: true, - }) - .catch((e) => console.log(e)); - - interaction.reply({ content: `> Added <@${added.id}> to the ticket` }).catch((e) => console.log(e)); - - log( - { - LogType: "userAdded", - user: interaction.user, - ticketId: ticket.id.toString(), - ticketChannelId: interaction.channel?.id, - target: added, - }, - this.client - ); - } + public static data: SlashCommandBuilder = new SlashCommandBuilder() + .setName("add") + .setDescription("Add someone to the ticket") + .addUserOption((input) => input.setName("user").setDescription("The user to add").setRequired(true)); + constructor(client: ExtendedClient) { + super(client); + } + + async execute(interaction: CommandInteraction) { + + const added = interaction.options.getUser("user", true); + const ticket = await this.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)); + + 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 (invited.length >= 25) + return interaction.reply({ content: "You can't add more than 25 users", ephemeral: true }).catch((e) => console.log(e)); + + invited.push(added.id); + await this.client.prisma.tickets.update({ + data: { + invited: JSON.stringify(invited) + }, + where: { + channelid: interaction.channel?.id + } + }); + + await (interaction.channel as TextChannel | null)?.permissionOverwrites + .edit(added, { + SendMessages: true, + AddReactions: true, + ReadMessageHistory: true, + AttachFiles: true, + ViewChannel: true, + }) + .catch((e) => console.log(e)); + + interaction.reply({ content: `> Added <@${added.id}> to the ticket` }).catch((e) => console.log(e)); + + log( + { + LogType: "userAdded", + user: interaction.user, + ticketId: ticket.id.toString(), + ticketChannelId: interaction.channel?.id, + target: added, + }, + this.client + ); + } } /* diff --git a/src/commands/claim.ts b/src/commands/claim.ts index 491519c7..d3e86a97 100644 --- a/src/commands/claim.ts +++ b/src/commands/claim.ts @@ -19,15 +19,15 @@ limitations under the License. */ export default class ClaimCommand extends BaseCommand { - public static data: SlashCommandBuilder = new SlashCommandBuilder() - .setName("claim").setDescription("Set the ticket as claimed."); - constructor(client: ExtendedClient) { - super(client); - } - - async execute(interaction: CommandInteraction) { - return claim(interaction, this.client); - } + public static data: SlashCommandBuilder = new SlashCommandBuilder() + .setName("claim").setDescription("Set the ticket as claimed."); + constructor(client: ExtendedClient) { + super(client); + } + + async execute(interaction: CommandInteraction) { + return claim(interaction, this.client); + } } /* diff --git a/src/commands/close.ts b/src/commands/close.ts index e0466e8d..aae21b02 100644 --- a/src/commands/close.ts +++ b/src/commands/close.ts @@ -20,30 +20,30 @@ limitations under the License. */ export default class CloseCommand extends BaseCommand { - public static data: SlashCommandBuilder = new SlashCommandBuilder() - .setName("close").setDescription("Close the ticket"); - constructor(client: ExtendedClient) { - super(client); - } - - async execute(interaction: CommandInteraction) { - if ( - this.client.config.closeOption.whoCanCloseTicket === "STAFFONLY" && + public static data: SlashCommandBuilder = new SlashCommandBuilder() + .setName("close").setDescription("Close the ticket"); + constructor(client: ExtendedClient) { + super(client); + } + + async execute(interaction: CommandInteraction) { + if ( + this.client.config.closeOption.whoCanCloseTicket === "STAFFONLY" && !(interaction.member as GuildMember | null)?.roles.cache.some((r) => this.client.config.rolesWhoHaveAccessToTheTickets.includes(r.id)) - ) - return interaction - .reply({ - content: this.client.locales.ticketOnlyClosableByStaff, - ephemeral: true, - }) - .catch((e) => console.log(e)); - - if (this.client.config.closeOption.askReason) { - closeAskReason(interaction, this.client); - } else { - await interaction.deferReply().catch((e) => console.log(e)); - close(interaction, this.client); - } } + ) + return interaction + .reply({ + content: this.client.locales.ticketOnlyClosableByStaff, + ephemeral: true, + }) + .catch((e) => console.log(e)); + + if (this.client.config.closeOption.askReason) { + closeAskReason(interaction, this.client); + } else { + await interaction.deferReply().catch((e) => console.log(e)); + close(interaction, this.client); + } } } /* diff --git a/src/commands/index.ts b/src/commands/index.ts index 67e0b5f1..f4887e1a 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -5,9 +5,9 @@ import RemoveCommand from "./remove"; import RenameCommand from "./rename"; export { - AddCommand, - ClaimCommand, - CloseCommand, - RemoveCommand, - RenameCommand + AddCommand, + ClaimCommand, + CloseCommand, + RemoveCommand, + RenameCommand }; \ No newline at end of file diff --git a/src/commands/remove.ts b/src/commands/remove.ts index ee9f54c9..bb52e11d 100644 --- a/src/commands/remove.ts +++ b/src/commands/remove.ts @@ -18,49 +18,49 @@ limitations under the License. */ export default class RemoveCommand extends BaseCommand { - public static data: SlashCommandBuilder = new SlashCommandBuilder() - .setName("remove").setDescription("Remove someone from the ticket"); - constructor(client: ExtendedClient) { - super(client); - } + public static data: SlashCommandBuilder = new SlashCommandBuilder() + .setName("remove").setDescription("Remove someone from the ticket"); + constructor(client: ExtendedClient) { + super(client); + } - async execute(interaction: CommandInteraction) { - const ticket = await this.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)); + async execute(interaction: CommandInteraction) { + const ticket = await this.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)); - 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)); + 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)); - const addedUsers: User[] = []; - for (let i = 0; i < invited.length; i++) { - addedUsers.push(await this.client.users.fetch(invited[i])); - } + const addedUsers: User[] = []; + for (let i = 0; i < invited.length; i++) { + addedUsers.push(await this.client.users.fetch(invited[i])); + } - const row = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId("removeUser") - .setPlaceholder("Please select a user to remove") - .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, - value: user.id, - }; - }) - ) - ); + const row = new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId("removeUser") + .setPlaceholder("Please select a user to remove") + .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, + value: user.id, + }; + }) + ) + ); - interaction.reply({ components: [row] }).catch((e) => console.log(e)); } + interaction.reply({ components: [row] }).catch((e) => console.log(e)); } } /* diff --git a/src/commands/rename.ts b/src/commands/rename.ts index 06c6d2e6..674ef497 100644 --- a/src/commands/rename.ts +++ b/src/commands/rename.ts @@ -18,33 +18,33 @@ limitations under the License. */ export default class RenameCommand extends BaseCommand { - public static data: SlashCommandBuilder = new SlashCommandBuilder() - .setName("rename") - .setDescription("Rename the ticket") - .addStringOption((option) => option.setName("name").setDescription("The new name of the ticket").setRequired(true)); - constructor(client: ExtendedClient) { - super(client); - } + public static data: SlashCommandBuilder = new SlashCommandBuilder() + .setName("rename") + .setDescription("Rename the ticket") + .addStringOption((option) => option.setName("name").setDescription("The new name of the ticket").setRequired(true)); + constructor(client: ExtendedClient) { + super(client); + } - async execute(interaction: CommandInteraction) { - const ticket = await this.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) => this.client.config.rolesWhoHaveAccessToTheTickets.includes(r.id))) - return interaction - .reply({ - content: this.client.locales.ticketOnlyRenamableByStaff, - ephemeral: true, - }) - .catch((e) => console.log(e)); + async execute(interaction: CommandInteraction) { + const ticket = await this.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) => this.client.config.rolesWhoHaveAccessToTheTickets.includes(r.id))) + return interaction + .reply({ + content: this.client.locales.ticketOnlyRenamableByStaff, + ephemeral: true, + }) + .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: this.client.locales.ticketRenamed.replace("NEWNAME", (interaction.channel as TextChannel | null)?.toString() ?? "Unknown"), ephemeral: false }) - .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: this.client.locales.ticketRenamed.replace("NEWNAME", (interaction.channel as TextChannel | null)?.toString() ?? "Unknown"), ephemeral: false }) + .catch((e) => console.log(e)); } } /* diff --git a/src/events/index.ts b/src/events/index.ts index 225087f9..8e1f6cf4 100644 --- a/src/events/index.ts +++ b/src/events/index.ts @@ -2,6 +2,6 @@ import InteractionCreateEvent from "./interactionCreate"; import ReadyEvent from "./ready"; export { - InteractionCreateEvent, - ReadyEvent + InteractionCreateEvent, + ReadyEvent }; \ No newline at end of file diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 687c5edf..3b5ac985 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -24,228 +24,228 @@ limitations under the License. */ export default class InteractionCreateEvent extends BaseEvent { - constructor(client: ExtendedClient) { - super(client); - } - - public async execute(interaction: Interaction): Promise { - if (interaction.isChatInputCommand()) { - const command = this.client.commands.get(interaction.commandName); - if (!command) return; - - try { - await command.execute(interaction); - } catch (error) { - console.error(error); - await interaction.reply({ - content: "There was an error while executing this command!", - ephemeral: true - }); - } - } - - if (interaction.isButton()) { - if (interaction.customId === "openTicket") { - await interaction.deferReply({ ephemeral: true }).catch((e) => console.log(e)); - - const tCount = this.client.config.ticketTypes.length; - if(tCount === 0 || tCount > 25) { - await interaction.followUp({content: this.client.locales.invalidConfig, ephemeral: true}); - throw new Error("ticketTypes either has nothing or exceeded 25 entries. Please check the config and restart the bot"); - } - - for (const role of this.client.config.rolesWhoCanNotCreateTickets) { - if (role && (interaction.member as GuildMember | null)?.roles.cache.has(role)) { - interaction - .editReply({ - content: "You can't create a ticket because you are blacklisted" - }) - .catch((e) => console.log(e)); - return; - } - } + constructor(client: ExtendedClient) { + super(client); + } + + public async execute(interaction: Interaction): Promise { + if (interaction.isChatInputCommand()) { + const command = this.client.commands.get(interaction.commandName); + if (!command) return; + + try { + await command.execute(interaction); + } catch (error) { + console.error(error); + await interaction.reply({ + content: "There was an error while executing this command!", + ephemeral: true + }); + } + } + + if (interaction.isButton()) { + if (interaction.customId === "openTicket") { + await interaction.deferReply({ ephemeral: true }).catch((e) => console.log(e)); + + const tCount = this.client.config.ticketTypes.length; + if(tCount === 0 || tCount > 25) { + await interaction.followUp({content: this.client.locales.invalidConfig, ephemeral: true}); + throw new Error("ticketTypes either has nothing or exceeded 25 entries. Please check the config and restart the bot"); + } + + for (const role of this.client.config.rolesWhoCanNotCreateTickets) { + if (role && (interaction.member as GuildMember | null)?.roles.cache.has(role)) { + interaction + .editReply({ + content: "You can't create a ticket because you are blacklisted" + }) + .catch((e) => console.log(e)); + return; + } + } - // Max Ticket - if (this.client.config.maxTicketOpened > 0) { - const ticketsOpened = (await this.client.prisma.$queryRaw<[{count: bigint}]> - `SELECT COUNT(*) as count FROM tickets WHERE closedby IS NULL AND creator = ${interaction.user.id}`)[0].count; - - // If maxTicketOpened is 0, it means that there is no limit - if (ticketsOpened >= this.client.config.maxTicketOpened) { - interaction - .editReply({ - content: this.client.locales.ticketLimitReached.replace("TICKETLIMIT", this.client.config.maxTicketOpened.toString()) - }) - .catch((e) => console.log(e)); - return; - } - } - - // Make a select menus of all tickets types - let options: SelectMenuComponentOptionData[] = []; - - for (const x of this.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: SelectMenuComponentOptionData = { - label: x.name, - value: x.codeName, - }; - if (x.description) a.description = x.description; - if (x.emoji) a.emoji = x.emoji; - options.push(a); - } - - for (const x of options) { - const option = this.client.config.ticketTypes.filter((y) => y.codeName === x.value)[0]; - if (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); - } - } - } - } - - if (options.length <= 0) { - interaction.editReply({ - content: this.client.locales.noTickets - }); - return; - } - - const row = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId("selectTicketType") - .setPlaceholder(this.client.locales.other.selectTicketTypePlaceholder) - .setMaxValues(1) - .addOptions(options) - ); - - interaction - .editReply({ - components: [row], - }) - .catch((e) => console.log(e)); - } - - if (interaction.customId === "claim") { - claim(interaction, this.client); - } - - if (interaction.customId === "close") { - await interaction.deferReply({ ephemeral: true }).catch((e) => console.log(e)); - close(interaction, this.client, this.client.locales.other.noReasonGiven); - } - - if (interaction.customId === "close_askReason") { - closeAskReason(interaction, this.client); - } - - if (interaction.customId === "deleteTicket") { - deleteTicket(interaction, this.client); - } - } - - if (interaction.isStringSelectMenu()) { - if (interaction.customId === "selectTicketType") { - if (this.client.config.maxTicketOpened > 0) { - const ticketsOpened = (await this.client.prisma.$queryRaw<[{count: bigint}]> - `SELECT COUNT(*) as count FROM tickets WHERE closedby IS NULL AND creator = ${interaction.user.id}`)[0].count; - // If maxTicketOpened is 0, it means that there is no limit - if (ticketsOpened >= this.client.config.maxTicketOpened) { - interaction - .reply({ - content: this.client.locales.ticketLimitReached.replace("TICKETLIMIT", this.client.config.maxTicketOpened.toString()), - ephemeral: true, - }) - .catch((e) => console.log(e)); - return; - } - } - - const ticketType = this.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) { - // Sanity Check - const qCount = ticketType.questions.length; - if(qCount === 0 || qCount > 5) - throw new Error(`${ticketType.codeName} has either no questions or exceeded 5 questions. Check your config and restart the bot`); - - const modal = new ModalBuilder().setCustomId("askReason").setTitle(this.client.locales.modals.reasonTicketOpen.title); - for (const question of ticketType.questions) { - const index = ticketType.questions.indexOf(question); - const input = new TextInputBuilder() - .setCustomId(`input_${interaction.values[0]}_${index}`) - .setLabel(question.label) - .setStyle(question.style == "SHORT" ? TextInputStyle.Short : TextInputStyle.Paragraph) - .setPlaceholder(question.placeholder) - .setMaxLength(question.maxLength); - - const firstActionRow = new ActionRowBuilder().addComponents(input); - modal.addComponents(firstActionRow); - } - - await interaction.showModal(modal).catch((e) => console.log(e)); - } else { - createTicket(interaction, this.client, ticketType, this.client.locales.other.noReasonGiven); - } - } - - if (interaction.customId === "removeUser") { - const ticket = await this.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)); - - log( - { - LogType: "userRemoved", - user: interaction.user, - ticketId: ticket?.id.toString(), - ticketChannelId: interaction.channel?.id, - target: { - id: value, - }, - }, - this.client - ); - }); - - interaction - .update({ - content: `> Removed ${ - interaction.values.length < 1 ? interaction.values : interaction.values.map((a) => `<@${a}>`).join(", ") - } from the ticket`, - components: [], - }) - .catch((e) => console.log(e)); - } - } - - if (interaction.isModalSubmit()) { - if (interaction.customId === "askReason") { - const type = interaction.fields.fields.first()?.customId.split("_")[1]; - const ticketType = this.client.config.ticketTypes.find((x) => x.codeName === type); - // Using customId until the value can be figured out - if (!ticketType) return console.error(`Ticket type ${interaction.customId} not found!`); - createTicket(interaction, this.client, ticketType, interaction.fields.fields); - } - - if (interaction.customId === "askReasonClose") { - await interaction.deferReply().catch((e) => console.log(e)); - close(interaction, this.client, interaction.fields.fields.first()?.value); - } - } - } + // Max Ticket + if (this.client.config.maxTicketOpened > 0) { + const ticketsOpened = (await this.client.prisma.$queryRaw<[{count: bigint}]> + `SELECT COUNT(*) as count FROM tickets WHERE closedby IS NULL AND creator = ${interaction.user.id}`)[0].count; + + // If maxTicketOpened is 0, it means that there is no limit + if (ticketsOpened >= this.client.config.maxTicketOpened) { + interaction + .editReply({ + content: this.client.locales.ticketLimitReached.replace("TICKETLIMIT", this.client.config.maxTicketOpened.toString()) + }) + .catch((e) => console.log(e)); + return; + } + } + + // Make a select menus of all tickets types + let options: SelectMenuComponentOptionData[] = []; + + for (const x of this.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: SelectMenuComponentOptionData = { + label: x.name, + value: x.codeName, + }; + if (x.description) a.description = x.description; + if (x.emoji) a.emoji = x.emoji; + options.push(a); + } + + for (const x of options) { + const option = this.client.config.ticketTypes.filter((y) => y.codeName === x.value)[0]; + if (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); + } + } + } + } + + if (options.length <= 0) { + interaction.editReply({ + content: this.client.locales.noTickets + }); + return; + } + + const row = new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId("selectTicketType") + .setPlaceholder(this.client.locales.other.selectTicketTypePlaceholder) + .setMaxValues(1) + .addOptions(options) + ); + + interaction + .editReply({ + components: [row], + }) + .catch((e) => console.log(e)); + } + + if (interaction.customId === "claim") { + claim(interaction, this.client); + } + + if (interaction.customId === "close") { + await interaction.deferReply({ ephemeral: true }).catch((e) => console.log(e)); + close(interaction, this.client, this.client.locales.other.noReasonGiven); + } + + if (interaction.customId === "close_askReason") { + closeAskReason(interaction, this.client); + } + + if (interaction.customId === "deleteTicket") { + deleteTicket(interaction, this.client); + } + } + + if (interaction.isStringSelectMenu()) { + if (interaction.customId === "selectTicketType") { + if (this.client.config.maxTicketOpened > 0) { + const ticketsOpened = (await this.client.prisma.$queryRaw<[{count: bigint}]> + `SELECT COUNT(*) as count FROM tickets WHERE closedby IS NULL AND creator = ${interaction.user.id}`)[0].count; + // If maxTicketOpened is 0, it means that there is no limit + if (ticketsOpened >= this.client.config.maxTicketOpened) { + interaction + .reply({ + content: this.client.locales.ticketLimitReached.replace("TICKETLIMIT", this.client.config.maxTicketOpened.toString()), + ephemeral: true, + }) + .catch((e) => console.log(e)); + return; + } + } + + const ticketType = this.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) { + // Sanity Check + const qCount = ticketType.questions.length; + if(qCount === 0 || qCount > 5) + throw new Error(`${ticketType.codeName} has either no questions or exceeded 5 questions. Check your config and restart the bot`); + + const modal = new ModalBuilder().setCustomId("askReason").setTitle(this.client.locales.modals.reasonTicketOpen.title); + for (const question of ticketType.questions) { + const index = ticketType.questions.indexOf(question); + const input = new TextInputBuilder() + .setCustomId(`input_${interaction.values[0]}_${index}`) + .setLabel(question.label) + .setStyle(question.style == "SHORT" ? TextInputStyle.Short : TextInputStyle.Paragraph) + .setPlaceholder(question.placeholder) + .setMaxLength(question.maxLength); + + const firstActionRow = new ActionRowBuilder().addComponents(input); + modal.addComponents(firstActionRow); + } + + await interaction.showModal(modal).catch((e) => console.log(e)); + } else { + createTicket(interaction, this.client, ticketType, this.client.locales.other.noReasonGiven); + } + } + + if (interaction.customId === "removeUser") { + const ticket = await this.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)); + + log( + { + LogType: "userRemoved", + user: interaction.user, + ticketId: ticket?.id.toString(), + ticketChannelId: interaction.channel?.id, + target: { + id: value, + }, + }, + this.client + ); + }); + + interaction + .update({ + content: `> Removed ${ + interaction.values.length < 1 ? interaction.values : interaction.values.map((a) => `<@${a}>`).join(", ") + } from the ticket`, + components: [], + }) + .catch((e) => console.log(e)); + } + } + + if (interaction.isModalSubmit()) { + if (interaction.customId === "askReason") { + const type = interaction.fields.fields.first()?.customId.split("_")[1]; + const ticketType = this.client.config.ticketTypes.find((x) => x.codeName === type); + // Using customId until the value can be figured out + if (!ticketType) return console.error(`Ticket type ${interaction.customId} not found!`); + createTicket(interaction, this.client, ticketType, interaction.fields.fields); + } + + if (interaction.customId === "askReasonClose") { + await interaction.deferReply().catch((e) => console.log(e)); + close(interaction, this.client, interaction.fields.fields.first()?.value); + } + } + } } /* diff --git a/src/events/ready.ts b/src/events/ready.ts index 70c981f5..01c16950 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -23,131 +23,131 @@ limitations under the License. */ export default class ReadyEvent extends BaseEvent { - private connected = false; - constructor(client: ExtendedClient) { - super(client); - } - - public async execute() { - if (!this.client.config.guildId) { - console.log("⚠️⚠️⚠️ Please add the guild id in the config.jsonc file. ⚠️⚠️⚠️"); - process.exit(0); - } - - await this.client.guilds.fetch(this.client.config.guildId); - await this.client.guilds.cache.get(this.client.config.guildId)?.members.fetch(); - if (!this.client.guilds.cache.get(this.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); - } - - const embedMessageId = (await this.client.prisma.config.findUnique({ - where: { - key: "openTicketMessageId", - } - }))?.value; - await this.client.channels.fetch(this.client.config.openTicketChannelId).catch(() => { - console.error("The channel to open tickets is not found!"); - process.exit(0); - }); - const openTicketChannel = await this.client.channels.cache.get(this.client.config.openTicketChannelId); - if (!openTicketChannel) { - console.error("The channel to open tickets is not found!"); - process.exit(0); - } - - if (!openTicketChannel.isTextBased()) { - console.error("The channel to open tickets is not a channel!"); - process.exit(0); - } - - const embedDat = {...this.client.locales.embeds.openTicket}; - const footer = embedDat.footer.text.replace("ticket.pm", ""); - // 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 ?? this.client.config.mainColor); - - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder().setCustomId("openTicket").setLabel(this.client.locales.other.openTicketButtonMSG).setStyle(ButtonStyle.Primary) - ); - - try { - // Fetch Message object and return undefined if not found - const msg = embedMessageId ? await (()=> new Promise((res)=> { - openTicketChannel?.messages?.fetch(embedMessageId) - .then(msg=>res(msg)) - .catch(()=>res(undefined)); - }))() : undefined; + private connected = false; + constructor(client: ExtendedClient) { + super(client); + } + + public async execute() { + if (!this.client.config.guildId) { + console.log("⚠️⚠️⚠️ Please add the guild id in the config.jsonc file. ⚠️⚠️⚠️"); + process.exit(0); + } + + await this.client.guilds.fetch(this.client.config.guildId); + await this.client.guilds.cache.get(this.client.config.guildId)?.members.fetch(); + if (!this.client.guilds.cache.get(this.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); + } + + const embedMessageId = (await this.client.prisma.config.findUnique({ + where: { + key: "openTicketMessageId", + } + }))?.value; + await this.client.channels.fetch(this.client.config.openTicketChannelId).catch(() => { + console.error("The channel to open tickets is not found!"); + process.exit(0); + }); + const openTicketChannel = await this.client.channels.cache.get(this.client.config.openTicketChannelId); + if (!openTicketChannel) { + console.error("The channel to open tickets is not found!"); + process.exit(0); + } + + if (!openTicketChannel.isTextBased()) { + console.error("The channel to open tickets is not a channel!"); + process.exit(0); + } + + const embedDat = {...this.client.locales.embeds.openTicket}; + const footer = embedDat.footer.text.replace("ticket.pm", ""); + // 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 ?? this.client.config.mainColor); + + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder().setCustomId("openTicket").setLabel(this.client.locales.other.openTicketButtonMSG).setStyle(ButtonStyle.Primary) + ); + + try { + // Fetch Message object and return undefined if not found + const msg = embedMessageId ? await (()=> new Promise((res)=> { + openTicketChannel?.messages?.fetch(embedMessageId) + .then(msg=>res(msg)) + .catch(()=>res(undefined)); + }))() : undefined; - if (msg && msg.id) { - msg.edit({ - embeds: [embed], - components: [row] - }); - } else { - const channel = this.client.channels.cache.get(this.client.config.openTicketChannelId); - if(!channel || !channel.isTextBased()) return console.error("Invalid openTicketChannelId"); - channel.send({ - embeds: [embed], - components: [row] - }).then((rMsg) => { - this.client.prisma.config.upsert({ - create: { - key: "openTicketMessageId", - value: rMsg.id - }, - update: { - value: rMsg.id - }, - where: { - key: "openTicketMessageId" - } - }).then(); // I need .then() for it to execute?!?!?? - }); - } - } catch (e) { - console.error(e); - } - - - this.setStatus(); - setInterval(()=>this.setStatus(), 9e5); // 15 minutes - - readline.cursorTo(process.stdout, 0); - process.stdout.write( - `\x1b[0m🚀 The bot is ready! Logged in as \x1b[37;46;1m${this.client.user?.tag}\x1b[0m (\x1b[37;46;1m${this.client.user?.id}\x1b[0m) + if (msg && msg.id) { + msg.edit({ + embeds: [embed], + components: [row] + }); + } else { + const channel = this.client.channels.cache.get(this.client.config.openTicketChannelId); + if(!channel || !channel.isTextBased()) return console.error("Invalid openTicketChannelId"); + channel.send({ + embeds: [embed], + components: [row] + }).then((rMsg) => { + this.client.prisma.config.upsert({ + create: { + key: "openTicketMessageId", + value: rMsg.id + }, + update: { + value: rMsg.id + }, + where: { + key: "openTicketMessageId" + } + }).then(); // I need .then() for it to execute?!?!?? + }); + } + } catch (e) { + console.error(e); + } + + + this.setStatus(); + setInterval(()=>this.setStatus(), 9e5); // 15 minutes + + readline.cursorTo(process.stdout, 0); + process.stdout.write( + `\x1b[0m🚀 The bot is ready! Logged in as \x1b[37;46;1m${this.client.user?.tag}\x1b[0m (\x1b[37;46;1m${this.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⛅ 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(() => {return;}); - if (a) { - const sponsors: SponsorType[] = a.data; - 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`); - } - - - if ((await this.client.prisma.config.findUnique({ - where: { - key: "firstStart", - } - })) === null) { - await this.client.prisma.config.create({ - data: { - key: "firstStart", - value: "true", - } - }); - - if(!this.client.config.minimalTracking) console.warn(` + ); + + const a = await axios.get("https://raw.githubusercontent.com/Sayrix/sponsors/main/sponsors.json").catch(() => {return;}); + if (a) { + const sponsors: SponsorType[] = a.data; + 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`); + } + + + if ((await this.client.prisma.config.findUnique({ + where: { + key: "firstStart", + } + })) === null) { + await this.client.prisma.config.create({ + data: { + key: "firstStart", + value: "true", + } + }); + + if(!this.client.config.minimalTracking) console.warn(` PRIVACY NOTICES ------------------------------- Telemetry is current set to full and the following information are sent to the server anonymously: @@ -163,7 +163,7 @@ export default class ReadyEvent extends BaseEvent { ------------------------------- 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(` + else console.warn(` PRIVACY NOTICES ------------------------------- Minimal tracking has been enabled; the following information are sent anonymously: @@ -171,129 +171,129 @@ export default class ReadyEvent extends BaseEvent { * NodeJS Version ------------------------------- `.replace(/\t/g, "")); - } - - this.connect(); - - this.client.deployCommands(); - } - - private setStatus(): void { - if (this.client.config.status) { - if (!this.client.config.status.enabled) return; - - let type = 0; - switch(this.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 (this.client.config.status.type && this.client.config.status.text) { - // If the user just want to set the status but not the activity - const url = this.client.config.status.url; - this.client.user?.setPresence({ - activities: [{ name: this.client.config.status.text, type: type, url: (url && url.trim() !== "") ? url : undefined }], - status: this.client.config.status.status, - }); - } - this.client.user?.setStatus(this.client.config.status.status); - } - } - - private connect(): void { - if (this.connected) return; - const ws = new WebSocketClient(); - - ws.on("connectFailed", (e) => { - this.connected = false; - setTimeout(()=>this.connect(), Math.random() * 1e4); - console.log(`❌ WebSocket Error: ${e.toString()}`); - }); - - ws.on("connect", (connection) => { - connection.on("error", (e) => { - this.connected = false; - setTimeout(()=>this.connect(), Math.random() * 1e4); - console.log(`❌ WebSocket Error: ${e.toString()}`); - }); - - connection.on("close", (e) => { - this.connected = false; - setTimeout(()=>this.connect(), Math.random() * 1e4); - console.log(`❌ WebSocket Error: ${e.toString()}`); - }); - - this.connected = true; - console.log("✅ Connected to WebSocket server."); - this.telemetry(connection); - - setInterval(() => { - this.telemetry(connection); - }, 120_000); - }); - - ws.connect("wss://ws.ticket.pm", "echo-protocol"); - - } - - private 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: this.client?.user?.tag, - clientId: this.client?.user?.id, - guildId: this.client?.config?.guildId - }; - // Minimal tracking enabled, remove those info from being sent - if(this.client.config.minimalTracking) { - fullInfo = {}; - moreInfo = {}; - } - connection.sendUTF( - JSON.stringify({ - type: "telemetry", - data: { - stats: { - guilds: this.client?.guilds?.cache?.size, - users: this.client?.users?.cache?.size - }, - infos: { - // eslint-disable-next-line @typescript-eslint/no-var-requires - ticketbotVersion: require("../../package.json").version, - nodeVersion: process.version, - ...fullInfo - }, - ...moreInfo - } - }) - ); - } + } + + this.connect(); + + this.client.deployCommands(); + } + + private setStatus(): void { + if (this.client.config.status) { + if (!this.client.config.status.enabled) return; + + let type = 0; + switch(this.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 (this.client.config.status.type && this.client.config.status.text) { + // If the user just want to set the status but not the activity + const url = this.client.config.status.url; + this.client.user?.setPresence({ + activities: [{ name: this.client.config.status.text, type: type, url: (url && url.trim() !== "") ? url : undefined }], + status: this.client.config.status.status, + }); + } + this.client.user?.setStatus(this.client.config.status.status); + } + } + + private connect(): void { + if (this.connected) return; + const ws = new WebSocketClient(); + + ws.on("connectFailed", (e) => { + this.connected = false; + setTimeout(()=>this.connect(), Math.random() * 1e4); + console.log(`❌ WebSocket Error: ${e.toString()}`); + }); + + ws.on("connect", (connection) => { + connection.on("error", (e) => { + this.connected = false; + setTimeout(()=>this.connect(), Math.random() * 1e4); + console.log(`❌ WebSocket Error: ${e.toString()}`); + }); + + connection.on("close", (e) => { + this.connected = false; + setTimeout(()=>this.connect(), Math.random() * 1e4); + console.log(`❌ WebSocket Error: ${e.toString()}`); + }); + + this.connected = true; + console.log("✅ Connected to WebSocket server."); + this.telemetry(connection); + + setInterval(() => { + this.telemetry(connection); + }, 120_000); + }); + + ws.connect("wss://ws.ticket.pm", "echo-protocol"); + + } + + private 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: this.client?.user?.tag, + clientId: this.client?.user?.id, + guildId: this.client?.config?.guildId + }; + // Minimal tracking enabled, remove those info from being sent + if(this.client.config.minimalTracking) { + fullInfo = {}; + moreInfo = {}; + } + connection.sendUTF( + JSON.stringify({ + type: "telemetry", + data: { + stats: { + guilds: this.client?.guilds?.cache?.size, + users: this.client?.users?.cache?.size + }, + infos: { + // eslint-disable-next-line @typescript-eslint/no-var-requires + ticketbotVersion: require("../../package.json").version, + nodeVersion: process.version, + ...fullInfo + }, + ...moreInfo + } + }) + ); + } } /* diff --git a/src/index.ts b/src/index.ts index c8dc9862..d0ecbd19 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,11 +27,11 @@ 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) => { - console.error(reason, promise, a); + console.error(reason, promise, a); }); process.on("uncaughtException", (err: string) => { - console.error(err); + console.error(err); }); process.stdout.write(` @@ -48,36 +48,36 @@ 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("🔄 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)); - // 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] || + 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)); + // 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] || (latest[0] === current[0] && latest[1] > current[1]) || (latest[0] === current[0] && latest[1] === current[1] && latest[2] > current[2]) - ) - console.warn(`🔄 New version available: ${json[0].name}; Current Version: ${current.join(".")}`); - else console.log("🔄 The ticket-bot is 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"); + }); }); const config: ConfigType = jsonc.parse(fs.readFileSync(path.join(__dirname, "/../config/config.jsonc"), "utf8")); const client = new ExtendedClient({ - intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMembers], - presence: { - status: config.status?.status ?? "online" - } + intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMembers], + presence: { + status: config.status?.status ?? "online" + } }, config); // Login the bot const token = process.env["TOKEN"]; if(!token || token.trim() === "") - throw new Error("TOKEN Environment Not Found"); + throw new Error("TOKEN Environment Not Found"); client.login(process.env["TOKEN"]).then(null); /* diff --git a/src/structure/BaseCommand.ts b/src/structure/BaseCommand.ts index 1ef13c65..a09fa7d8 100644 --- a/src/structure/BaseCommand.ts +++ b/src/structure/BaseCommand.ts @@ -2,11 +2,11 @@ import {CommandInteraction, InteractionResponse, SlashCommandBuilder} from "disc import {ExtendedClient} from "./"; export default abstract class BaseCommand { - public static data: SlashCommandBuilder; - protected client: ExtendedClient; - protected constructor(client: ExtendedClient) { - this.client = client; - } + public static data: SlashCommandBuilder; + protected client: ExtendedClient; + protected constructor(client: ExtendedClient) { + this.client = client; + } // eslint-disable-next-line no-unused-vars abstract execute(interaction: CommandInteraction): void | Promise>; diff --git a/src/structure/BaseEvent.ts b/src/structure/BaseEvent.ts index 85912ec2..f789cdf1 100644 --- a/src/structure/BaseEvent.ts +++ b/src/structure/BaseEvent.ts @@ -2,12 +2,12 @@ import {ExtendedClient} from "./"; import {ClientEvents} from "discord.js"; export default abstract class BaseEvent { - protected readonly client: ExtendedClient; - protected constructor(client: ExtendedClient) { - this.client = client; - } + protected readonly client: ExtendedClient; + protected constructor(client: ExtendedClient) { + this.client = client; + } - // eslint-disable-next-line no-unused-vars + // eslint-disable-next-line no-unused-vars protected abstract execute(...args: ClientEvents[keyof ClientEvents]): void | Promise; } \ No newline at end of file diff --git a/src/structure/ExtendedClient.ts b/src/structure/ExtendedClient.ts index 0374176b..b9d4e7a9 100644 --- a/src/structure/ExtendedClient.ts +++ b/src/structure/ExtendedClient.ts @@ -9,71 +9,71 @@ import {jsonc} from "jsonc"; import {REST} from "@discordjs/rest"; export default class ExtendedClient extends Client { - public readonly config: ConfigType; - public readonly prisma: PrismaClient; - public locales: LocaleType; - public commands: Collection; - constructor(options: ClientOptions, config: ConfigType) { - super(options); + public readonly config: ConfigType; + public readonly prisma: PrismaClient; + public locales: LocaleType; + public commands: Collection; + constructor(options: ClientOptions, config: ConfigType) { + super(options); - this.config = config; - this.prisma = new PrismaClient(); - this.locales = JSON.parse(fs.readFileSync(path.join(__dirname, `../../locales/${this.config.lang}.json`), "utf8")); - this.commands = new Collection([ - [AddCommand.data.name, new AddCommand(this)], - [ClaimCommand.data.name, new ClaimCommand(this)], - [CloseCommand.data.name, new CloseCommand(this)], - [RemoveCommand.data.name, new RemoveCommand(this)], - [RenameCommand.data.name, new RenameCommand(this)], - ]); - this.loadEvents(); + this.config = config; + this.prisma = new PrismaClient(); + this.locales = JSON.parse(fs.readFileSync(path.join(__dirname, `../../locales/${this.config.lang}.json`), "utf8")); + this.commands = new Collection([ + [AddCommand.data.name, new AddCommand(this)], + [ClaimCommand.data.name, new ClaimCommand(this)], + [CloseCommand.data.name, new CloseCommand(this)], + [RemoveCommand.data.name, new RemoveCommand(this)], + [RenameCommand.data.name, new RenameCommand(this)], + ]); + this.loadEvents(); - } + } - public msToHm (ms: number | Date) { + public msToHm (ms: number | Date) { - if(ms instanceof Date) ms = ms.getTime(); + 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)); - const hoursms = ms % (60 * 60 * 1000); - const minutes = Math.floor(hoursms / (60 * 1000)); - const minutesms = ms % (60 * 1000); - const sec = Math.floor(minutesms / 1000); + const days = Math.floor(ms / (24 * 60 * 60 * 1000)); + const daysms = ms % (24 * 60 * 60 * 1000); + const hours = Math.floor(daysms / (60 * 60 * 1000)); + const hoursms = ms % (60 * 60 * 1000); + const minutes = Math.floor(hoursms / (60 * 1000)); + const minutesms = ms % (60 * 1000); + const sec = Math.floor(minutesms / 1000); - let result = "0s"; + let result = "0s"; - if (days > 0) result = `${days}d ${hours}h ${minutes}m ${sec}s`; - if (hours > 0) result = `${hours}h ${minutes}m ${sec}s`; - if (minutes > 0) result = `${minutes}m ${sec}s`; - if (sec > 0) result = `${sec}s`; - return result; + if (days > 0) result = `${days}d ${hours}h ${minutes}m ${sec}s`; + if (hours > 0) result = `${hours}h ${minutes}m ${sec}s`; + if (minutes > 0) result = `${minutes}m ${sec}s`; + if (sec > 0) result = `${sec}s`; + return result; - } + } - private loadEvents () { - this.on("interactionCreate", (interaction) => new InteractionCreateEvent(this).execute(interaction)); - this.on("ready", () => new ReadyEvent(this).execute()); - } + private loadEvents () { + this.on("interactionCreate", (interaction) => new InteractionCreateEvent(this).execute(interaction)); + this.on("ready", () => new ReadyEvent(this).execute()); + } - public deployCommands() { - const commands = [ - AddCommand.data.toJSON(), - ClaimCommand.data.toJSON(), - CloseCommand.data.toJSON(), - RemoveCommand.data.toJSON(), - RenameCommand.data.toJSON() - ]; + public deployCommands() { + const commands = [ + AddCommand.data.toJSON(), + ClaimCommand.data.toJSON(), + CloseCommand.data.toJSON(), + RemoveCommand.data.toJSON(), + RenameCommand.data.toJSON() + ]; - const { 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")); - if(!process.env["TOKEN"]) throw Error("Discord Token Expected, deploy-command"); - const rest = new REST({ version: "10" }).setToken(process.env["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(this.user?.id ?? "", guildId), { body: commands }) - .then(() => console.log("✅ Successfully registered application commands.")) - .catch(console.error); - } + rest + .put(Routes.applicationGuildCommands(this.user?.id ?? "", guildId), { body: commands }) + .then(() => console.log("✅ Successfully registered application commands.")) + .catch(console.error); + } } \ No newline at end of file diff --git a/src/structure/index.ts b/src/structure/index.ts index cae6ea13..37fdb498 100644 --- a/src/structure/index.ts +++ b/src/structure/index.ts @@ -4,7 +4,7 @@ import ExtendedClient from "./ExtendedClient"; export * from "./types"; export { - BaseCommand, - BaseEvent, - ExtendedClient, + BaseCommand, + BaseEvent, + ExtendedClient, }; \ No newline at end of file diff --git a/src/utils/claim.ts b/src/utils/claim.ts index 0d4b7a18..572d46ab 100644 --- a/src/utils/claim.ts +++ b/src/utils/claim.ts @@ -19,22 +19,22 @@ import { log } from "./logs"; import {ExtendedClient} from "../structure"; export const claim = async(interaction: ButtonInteraction | CommandInteraction, client: ExtendedClient) => { - let ticket = await client.prisma.tickets.findUnique({ - where: { - channelid: interaction.channel?.id - } - }); - const claimed = ticket?.claimedat && ticket.claimedby; - - if (!ticket) + let 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", 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, @@ -42,7 +42,7 @@ export const claim = async(interaction: ButtonInteraction | CommandInteraction, }) .catch((e) => console.log(e)); - if (claimed) + if (claimed) return interaction .reply({ content: client.locales.ticketAlreadyClaimed, @@ -50,44 +50,44 @@ export const claim = async(interaction: ButtonInteraction | CommandInteraction, }) .catch((e) => console.log(e)); - log( + log( { - LogType: "ticketClaim", + LogType: "ticketClaim", user: interaction.user, ticketId: ticket.id.toString(), ticketChannelId: interaction.channel?.id, ticketCreatedAt: ticket.createdat, }, client - ); - - ticket = await client.prisma.tickets.update({ - data: { - claimedby: interaction.user.id, - claimedat: Date.now() - }, - where: { - channelid: interaction.channel?.id, - } - }); - - 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}>`)}`); - - const row = new ActionRowBuilder(); - msg?.components[0].components.map((x) => { - const btnBuilder = new ButtonBuilder(x.data); + ); + + ticket = await client.prisma.tickets.update({ + data: { + claimedby: interaction.user.id, + claimedat: Date.now() + }, + where: { + channelid: interaction.channel?.id, + } + }); + + 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}>`)}`); + + 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); - }); + row.addComponents(btnBuilder); + }); msg?.edit({ content: msg.content, embeds: [newEmbed], components: [row], - }).catch((e) => console.log(e)); + }).catch((e) => console.log(e)); interaction .reply({ @@ -96,25 +96,25 @@ export const claim = async(interaction: ButtonInteraction | CommandInteraction, }) .catch((e) => console.log(e)); - const defaultName = client.config.claimOption.nameWhenClaimed; + const defaultName = client.config.claimOption.nameWhenClaimed; if (defaultName && defaultName.trim() !== "") { - const creatorUser = await client.users.fetch(ticket.creator); - const newName = defaultName - .replaceAll("S_USERNAME", interaction.user.username) - .replaceAll("U_USERNAME", creatorUser.username) - .replaceAll("S_USERID", interaction.user.id) - .replaceAll("U_USERID", creatorUser.id) - .replaceAll("TICKETCOUNT", ticket.id.toString()); + const creatorUser = await client.users.fetch(ticket.creator); + const newName = defaultName + .replaceAll("S_USERNAME", interaction.user.username) + .replaceAll("U_USERNAME", creatorUser.username) + .replaceAll("S_USERID", interaction.user.id) + .replaceAll("U_USERID", creatorUser.id) + .replaceAll("TICKETCOUNT", ticket.id.toString()); await (interaction.channel as TextChannel | null)?.setName(newName).catch((e) => console.log(e)); } - const categoryID = client.config.claimOption.categoryWhenClaimed; - if(categoryID && categoryID.trim() !== "") { - const category = await interaction.guild?.channels.fetch(categoryID); - if(category?.type !== ChannelType.GuildCategory) - return console.error("claim.ts: USER ERROR - Invalid categoryWhenClaimed ID. Channel must be a category."); - await (interaction.channel as TextChannel | null)?.setParent(category); - } + const categoryID = client.config.claimOption.categoryWhenClaimed; + if(categoryID && categoryID.trim() !== "") { + const category = await interaction.guild?.channels.fetch(categoryID); + if(category?.type !== ChannelType.GuildCategory) + return console.error("claim.ts: USER ERROR - Invalid categoryWhenClaimed ID. Channel must be a category."); + await (interaction.channel as TextChannel | null)?.setParent(category); + } }; /* Copyright 2023 Sayrix (github.com/Sayrix) diff --git a/src/utils/close.ts b/src/utils/close.ts index a91bc6b2..aac71756 100644 --- a/src/utils/close.ts +++ b/src/utils/close.ts @@ -40,202 +40,202 @@ type ticketType = { } export async function close(interaction: ButtonInteraction | CommandInteraction | ModalSubmitInteraction, client: ExtendedClient, reason?: string) { - if (!client.config.closeOption.createTranscript) domain = client.locales.other.unavailable; - - 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 ( - client.config.closeOption.whoCanCloseTicket === "STAFFONLY" && + if (!client.config.closeOption.createTranscript) domain = client.locales.other.unavailable; + + 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 ( + client.config.closeOption.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 (ticketClosed) - 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 - ); + ) + return interaction + .editReply({ + content: client.locales.ticketOnlyClosableByStaff + }) + .catch((e) => console.log(e)); + + if (ticketClosed) + 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 + ); - // Normally the user that closes the ticket will get posted here, but we'll do it when the ticket finalizes - - 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)); - invited.forEach(async (user) => { - (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)); - async function _close(id: string, ticket: ticketType) { - if (client.config.closeOption.closeTicketCategoryId) (interaction.channel as TextChannel | null)?.setParent(client.config.closeOption.closeTicketCategoryId).catch((e) => console.log(e)); - - const msg = await interaction.channel?.messages.fetch(ticket.messageid); - const embed = new EmbedBuilder(msg?.embeds[0].data); - - const rowAction = new ActionRowBuilder(); - msg?.components[0]?.components?.map((x) => { - 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, - embeds: [embed], - components: [rowAction] - }) - .catch((e) => console.log(e)); - - 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)); + // Normally the user that closes the ticket will get posted here, but we'll do it when the ticket finalizes + + 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)); + invited.forEach(async (user) => { + (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)); + async function _close(id: string, ticket: ticketType) { + if (client.config.closeOption.closeTicketCategoryId) (interaction.channel as TextChannel | null)?.setParent(client.config.closeOption.closeTicketCategoryId).catch((e) => console.log(e)); + + const msg = await interaction.channel?.messages.fetch(ticket.messageid); + const embed = new EmbedBuilder(msg?.embeds[0].data); + + const rowAction = new ActionRowBuilder(); + msg?.components[0]?.components?.map((x) => { + 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, + embeds: [embed], + components: [rowAction] + }) + .catch((e) => console.log(e)); + + 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)); - 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) - ); - const lEmbed = client.locales.embeds; - interaction.channel?.send({ - embeds: [ - JSON.parse( - JSON.stringify(lEmbed.ticketClosed) - .replace("TICKETCOUNT", ticket.id.toString()) - .replace("REASON", (ticket.closereason ?? client.locales.other.noReasonGiven).replace(/[\n\r]/g, "\\n")) - .replace("CLOSERNAME", interaction.user.tag) - ) - ], - components: [row] - }) - .catch((e) => console.log(e)); - - - if(!client.config.closeOption.dmUser) return; - const footer = lEmbed.ticketClosedDM.footer.text.replace("ticket.pm", ""); - const ticketClosedDMEmbed = new EmbedBuilder({ - ...lEmbed, - color: 0, - }) - .setColor(lEmbed.ticketClosedDM.color ?? client.config.mainColor) - .setDescription( - client.locales.embeds.ticketClosedDM.description - .replace("TICKETCOUNT", ticket.id.toString()) - .replace("TRANSCRIPTURL", `[\`${domain}${id}\`](${domain}${id})`) - .replace("REASON", ticket.closereason ?? client.locales.other.noReasonGiven) - .replace("CLOSERNAME", interaction.user.tag) - ) - .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: lEmbed.ticketClosedDM.footer.iconUrl - }); - - client.users.fetch(creator).then((user) => { - user - .send({ - embeds: [ticketClosedDMEmbed] - }) - .catch((e) => console.log(e)); - }); - } - - if (!client.config.closeOption.createTranscript) { - _close("", ticket); - return; - } - - async function fetchAll() { - const 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}&uuid=${client.config.uuidType}`, JSON.stringify(compressed), { - headers: { - "Content-Type": "application/json" - } - }) - .catch(console.error); - _close(ts?.data, ticket); - } - }); + 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) + ); + const lEmbed = client.locales.embeds; + interaction.channel?.send({ + embeds: [ + JSON.parse( + JSON.stringify(lEmbed.ticketClosed) + .replace("TICKETCOUNT", ticket.id.toString()) + .replace("REASON", (ticket.closereason ?? client.locales.other.noReasonGiven).replace(/[\n\r]/g, "\\n")) + .replace("CLOSERNAME", interaction.user.tag) + ) + ], + components: [row] + }) + .catch((e) => console.log(e)); + + + if(!client.config.closeOption.dmUser) return; + const footer = lEmbed.ticketClosedDM.footer.text.replace("ticket.pm", ""); + const ticketClosedDMEmbed = new EmbedBuilder({ + ...lEmbed, + color: 0, + }) + .setColor(lEmbed.ticketClosedDM.color ?? client.config.mainColor) + .setDescription( + client.locales.embeds.ticketClosedDM.description + .replace("TICKETCOUNT", ticket.id.toString()) + .replace("TRANSCRIPTURL", `[\`${domain}${id}\`](${domain}${id})`) + .replace("REASON", ticket.closereason ?? client.locales.other.noReasonGiven) + .replace("CLOSERNAME", interaction.user.tag) + ) + .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: lEmbed.ticketClosedDM.footer.iconUrl + }); + + client.users.fetch(creator).then((user) => { + user + .send({ + embeds: [ticketClosedDMEmbed] + }) + .catch((e) => console.log(e)); + }); + } + + if (!client.config.closeOption.createTranscript) { + _close("", ticket); + return; + } + + async function fetchAll() { + const 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}&uuid=${client.config.uuidType}`, JSON.stringify(compressed), { + headers: { + "Content-Type": "application/json" + } + }) + .catch(console.error); + _close(ts?.data, ticket); + } + }); } /* diff --git a/src/utils/close_askReason.ts b/src/utils/close_askReason.ts index 1336cb53..19b9e4e9 100644 --- a/src/utils/close_askReason.ts +++ b/src/utils/close_askReason.ts @@ -18,29 +18,29 @@ import { ActionRowBuilder, ButtonInteraction, CommandInteraction, GuildMember, M import {ExtendedClient} from "../structure"; export const closeAskReason = async(interaction: CommandInteraction | ButtonInteraction, client: ExtendedClient) => { - if ( - client.config.closeOption.whoCanCloseTicket === "STAFFONLY" && + if ( + client.config.closeOption.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 ModalBuilder().setCustomId("askReasonClose").setTitle(client.locales.modals.reasonTicketClose.title); - - const input = new TextInputBuilder() - .setCustomId("reason") - .setLabel(client.locales.modals.reasonTicketClose.label) - .setStyle(TextInputStyle.Paragraph) - .setPlaceholder(client.locales.modals.reasonTicketClose.placeholder) - .setMaxLength(256); - - const firstActionRow = new ActionRowBuilder().addComponents(input); - modal.addComponents(firstActionRow); - await interaction.showModal(modal).catch((e) => console.log(e)); + ) + return interaction + .reply({ + content: client.locales.ticketOnlyClosableByStaff, + ephemeral: true, + }) + .catch((e) => console.log(e)); + + const modal = new ModalBuilder().setCustomId("askReasonClose").setTitle(client.locales.modals.reasonTicketClose.title); + + const input = new TextInputBuilder() + .setCustomId("reason") + .setLabel(client.locales.modals.reasonTicketClose.label) + .setStyle(TextInputStyle.Paragraph) + .setPlaceholder(client.locales.modals.reasonTicketClose.placeholder) + .setMaxLength(256); + + const firstActionRow = new ActionRowBuilder().addComponents(input); + modal.addComponents(firstActionRow); + await interaction.showModal(modal).catch((e) => console.log(e)); }; /* diff --git a/src/utils/createTicket.ts b/src/utils/createTicket.ts index 5e5a1205..0c104f26 100644 --- a/src/utils/createTicket.ts +++ b/src/utils/createTicket.ts @@ -25,202 +25,202 @@ limitations under the License. * @param {Object|string} reasons */ export const createTicket = async (interaction: StringSelectMenuInteraction | ModalSubmitInteraction, client: ExtendedClient, 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)); - - const reason: string[] = []; - let allReasons = ""; - - if (typeof reasons === "object") { - reasons.forEach(async (r) => { - reason.push(r.value); - }); - allReasons = reason.map((r, i) => `Question ${i + 1}: ${r}`).join(", "); - } - if(typeof reasons === "string") allReasons = reasons; - - let ticketName = ""; - - let ticketCount = (await client.prisma.$queryRaw<[{count: bigint}]> - `SELECT COUNT(*) as count FROM tickets`)[0].count; - - if (ticketType.ticketNameOption) { - ticketName = ticketType.ticketNameOption - .replace("USERNAME", interaction.user.username) - .replace("USERID", interaction.user.id) - .replace("TICKETCOUNT", ticketCount.toString() ?? "0"); - } else { - ticketName = client.config.ticketNameOption - .replace("USERNAME", interaction.user.username) - .replace("USERID", interaction.user.id) - .replace("TICKETCOUNT", ticketCount.toString() ?? "0"); - } - if(!interaction.guild) return console.error("Interaction createTicket was not executed in a guild"); + // 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)); + + const reason: string[] = []; + let allReasons = ""; + + if (typeof reasons === "object") { + reasons.forEach(async (r) => { + reason.push(r.value); + }); + allReasons = reason.map((r, i) => `Question ${i + 1}: ${r}`).join(", "); + } + if(typeof reasons === "string") allReasons = reasons; + + let ticketName = ""; + + let ticketCount = (await client.prisma.$queryRaw<[{count: bigint}]> + `SELECT COUNT(*) as count FROM tickets`)[0].count; + + if (ticketType.ticketNameOption) { + ticketName = ticketType.ticketNameOption + .replace("USERNAME", interaction.user.username) + .replace("USERID", interaction.user.id) + .replace("TICKETCOUNT", ticketCount.toString() ?? "0"); + } else { + ticketName = client.config.ticketNameOption + .replace("USERNAME", interaction.user.username) + .replace("USERID", interaction.user.id) + .replace("TICKETCOUNT", ticketCount.toString() ?? "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, - deny: [PermissionFlagsBits.ViewChannel], - }, - ], - }); - - if (!channel) return reject("Couldn't create the ticket channel."); - log( - { - LogType: "ticketCreate", - user: interaction.user, - reason: allReasons, - ticketChannelId: channel.id - }, - client - ); - - // Client.db is set here and incremented ticket count - ticketCount++; - - 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 footer = lEmbeds.ticketOpened.footer.text.replace("ticket.pm", ""); - if(ticketType.color?.toString().trim() === "") ticketType.color = undefined; - const ticketOpenedEmbed = new EmbedBuilder({ - ...lEmbeds.ticketOpened, - color: 0, - }) - .setColor(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", ticketCount.toString() || "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", ticketCount.toString() || "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]) - ) - .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 - }); - - const row = new ActionRowBuilder(); - - if (client.config.closeOption.closeButton) { - if (client.config.closeOption.askReason) { - row.addComponents( - new ButtonBuilder() - .setCustomId("close_askReason") - .setLabel(client.locales.buttons.close.label) - .setEmoji(client.locales.buttons.close.emoji) - .setStyle(ButtonStyle.Danger) - ); - } else { - row.addComponents( - new ButtonBuilder() - .setCustomId("close") - .setLabel(client.locales.buttons.close.label) - .setEmoji(client.locales.buttons.close.emoji) - .setStyle(ButtonStyle.Danger) - ); - } - } - - if (client.config.claimOption.claimButton) { - row.addComponents( - new ButtonBuilder() - .setCustomId("claim") - .setLabel(client.locales.buttons.claim.label) - .setEmoji(client.locales.buttons.claim.emoji) - .setStyle(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.prisma.tickets.create({ - data: { - category: JSON.stringify(ticketType), - reason: allReasons, - creator: interaction.user.id, - createdat: Date.now(), - channelid: channel.id, - messageid: msg.id - } - }).then(); // Again why tf do I need .then()?!?!? - 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)); - }); + const channel = await client.guilds.cache.get(client.config.guildId)?.channels.create({ + name: ticketName, + parent: ticketType.categoryId, + permissionOverwrites: [ + { + id: interaction.guild.roles.everyone, + deny: [PermissionFlagsBits.ViewChannel], + }, + ], + }); + + if (!channel) return reject("Couldn't create the ticket channel."); + log( + { + LogType: "ticketCreate", + user: interaction.user, + reason: allReasons, + ticketChannelId: channel.id + }, + client + ); + + // Client.db is set here and incremented ticket count + ticketCount++; + + 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 footer = lEmbeds.ticketOpened.footer.text.replace("ticket.pm", ""); + if(ticketType.color?.toString().trim() === "") ticketType.color = undefined; + const ticketOpenedEmbed = new EmbedBuilder({ + ...lEmbeds.ticketOpened, + color: 0, + }) + .setColor(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", ticketCount.toString() || "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", ticketCount.toString() || "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]) + ) + .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 + }); + + const row = new ActionRowBuilder(); + + if (client.config.closeOption.closeButton) { + if (client.config.closeOption.askReason) { + row.addComponents( + new ButtonBuilder() + .setCustomId("close_askReason") + .setLabel(client.locales.buttons.close.label) + .setEmoji(client.locales.buttons.close.emoji) + .setStyle(ButtonStyle.Danger) + ); + } else { + row.addComponents( + new ButtonBuilder() + .setCustomId("close") + .setLabel(client.locales.buttons.close.label) + .setEmoji(client.locales.buttons.close.emoji) + .setStyle(ButtonStyle.Danger) + ); + } + } + + if (client.config.claimOption.claimButton) { + row.addComponents( + new ButtonBuilder() + .setCustomId("claim") + .setLabel(client.locales.buttons.claim.label) + .setEmoji(client.locales.buttons.claim.emoji) + .setStyle(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.prisma.tickets.create({ + data: { + category: JSON.stringify(ticketType), + reason: allReasons, + creator: interaction.user.id, + createdat: Date.now(), + channelid: channel.id, + messageid: msg.id + } + }).then(); // Again why tf do I need .then()?!?!? + 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.ts b/src/utils/delete.ts index 74f6a71f..be49cc6c 100644 --- a/src/utils/delete.ts +++ b/src/utils/delete.ts @@ -19,25 +19,25 @@ import { log } from "./logs"; import {ExtendedClient} from "../structure"; export const deleteTicket = async (interaction: ButtonInteraction, client: ExtendedClient) => { - 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.transcript ?? undefined, - }, - client - ); - await interaction.deferUpdate(); - interaction.channel?.delete().catch((e) => console.log(e)); + 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.transcript ?? undefined, + }, + client + ); + await interaction.deferUpdate(); + interaction.channel?.delete().catch((e) => console.log(e)); }; /* diff --git a/src/utils/logs.ts b/src/utils/logs.ts index 36a210b4..037d88f2 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -51,121 +51,121 @@ type log = { // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars export const log = async(logs: log, client: ExtendedClient) => { - 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(Number(BigInt(Date.now()) - BigInt(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(Number(BigInt(Date.now()) - BigInt(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(Number(BigInt(Date.now()) - BigInt(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(Number(BigInt(Date.now()) - BigInt(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(Number(BigInt(Date.now()) - BigInt(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(Number(BigInt(Date.now()) - BigInt(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)); + } }; /* From 752a9c836fd77c2db19bad964277499893ed7c01 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Tue, 18 Jul 2023 14:15:51 -0400 Subject: [PATCH 05/12] feat: Translation Module --- src/structure/ExtendedClient.ts | 7 ++-- src/utils/translation.ts | 59 +++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 src/utils/translation.ts diff --git a/src/structure/ExtendedClient.ts b/src/structure/ExtendedClient.ts index b9d4e7a9..1924ca91 100644 --- a/src/structure/ExtendedClient.ts +++ b/src/structure/ExtendedClient.ts @@ -1,5 +1,5 @@ import {Client, ClientOptions, Collection, Routes} from "discord.js"; -import {BaseCommand, ConfigType, LocaleType} from "./"; +import {BaseCommand, ConfigType} from "./"; import {PrismaClient} from "@prisma/client"; import fs from "fs-extra"; import path from "node:path"; @@ -7,18 +7,19 @@ import {AddCommand, ClaimCommand, CloseCommand, RemoveCommand, RenameCommand} fr import {InteractionCreateEvent, ReadyEvent} from "../events"; import {jsonc} from "jsonc"; import {REST} from "@discordjs/rest"; +import {Translation} from "../utils/translation"; export default class ExtendedClient extends Client { public readonly config: ConfigType; public readonly prisma: PrismaClient; - public locales: LocaleType; + public locales: Translation; public commands: Collection; constructor(options: ClientOptions, config: ConfigType) { super(options); this.config = config; this.prisma = new PrismaClient(); - this.locales = JSON.parse(fs.readFileSync(path.join(__dirname, `../../locales/${this.config.lang}.json`), "utf8")); + this.locales = new Translation(this.config.lang, path.join(__dirname, "../../locales/")); this.commands = new Collection([ [AddCommand.data.name, new AddCommand(this)], [ClaimCommand.data.name, new ClaimCommand(this)], diff --git a/src/utils/translation.ts b/src/utils/translation.ts new file mode 100644 index 00000000..b373c9b2 --- /dev/null +++ b/src/utils/translation.ts @@ -0,0 +1,59 @@ +/* +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/main/LICENSE +*/ + +import path from "node:path"; +import fs from "fs-extra"; + +export class Translation { + private primaryData: {[k: string]: string | undefined}; + private backupData?: {[k: string]: string | undefined}; + + /** + * locale handler module + * @param optName The locale file name (w/o extension) + * @param dir The directory of the locale files + */ + constructor(optName: string, dir?: string) { + dir = dir ?? "./locale"; + const fullDir = path.join(dir, `${optName}.json`); + if(!fs.existsSync(fullDir)) + throw new TranslationError("Translation file not found, check your config to verify if the name is correct or not"); + + this.primaryData = JSON.parse(fullDir); + if(optName !== "main") + this.backupData = JSON.parse(path.join(dir, "main.json")); + } + + /** + * Get the translation value or backup value if it doesn't exist + * @param key The object key the translation should pull + * @returns the translation data or throw error if the translation data cannot be found at all + */ + getValue(key: string): string { + // Try return the data from the main translation file + const main = this.primaryData[key]; + if(main) return main; + + // Pull backup and throw error if it doesn't exist + const backup = this.backupData && this.backupData[key]; + if(!backup) + throw new TranslationError(`TRANSLATION: Key '${key}' failed to pull backup translation. This indiciates this key data does not exist at all.`); + + // Return the backup translation + console.warn(`TRANSLATION: Key '${key}' is missing translation. If you can, please help fill in the translation and make PR for it.`); + return backup; + + + } +} + +export class TranslationError { + name: string = "TranslationError"; + message: string; + constructor(msg: string) { + this.message = msg; + } +} \ No newline at end of file From b13d4659a239dbbf516937e16ad6ec471a9f3f92 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Tue, 18 Jul 2023 14:24:21 -0400 Subject: [PATCH 06/12] fix: Attach @db.Text to all string value to avoid exceeding size issue --- prisma/docker.prisma | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/prisma/docker.prisma b/prisma/docker.prisma index 7306379f..3d784ed9 100644 --- a/prisma/docker.prisma +++ b/prisma/docker.prisma @@ -18,14 +18,14 @@ model tickets { channelid String @unique messageid String @unique category String @db.Text - invited String @default("[]") - reason String - creator String + invited String @default("[]") @db.Text + reason String @db.Text + creator String @db.Text createdat BigInt - claimedby String? + claimedby String? @db.Text claimedat BigInt? - closedby String? + closedby String? @db.Text closedat BigInt? - closereason String? - transcript String? + closereason String? @db.Text + transcript String? @db.Text } From 4f48863b5faf3b9bb79ee475c31a16415c6a0488 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Tue, 18 Jul 2023 16:11:48 -0400 Subject: [PATCH 07/12] feat: New translation module --- src/commands/close.ts | 2 +- src/commands/rename.ts | 4 ++-- src/events/interactionCreate.ts | 16 ++++++++-------- src/events/ready.ts | 4 ++-- src/structure/ExtendedClient.ts | 4 +++- src/utils/claim.ts | 8 ++++---- src/utils/close.ts | 24 ++++++++++++------------ src/utils/close_askReason.ts | 8 ++++---- src/utils/createTicket.ts | 16 ++++++++-------- src/utils/translation.ts | 27 ++++++++++++++++++++++++--- 10 files changed, 68 insertions(+), 45 deletions(-) diff --git a/src/commands/close.ts b/src/commands/close.ts index aae21b02..8d00ab9f 100644 --- a/src/commands/close.ts +++ b/src/commands/close.ts @@ -33,7 +33,7 @@ export default class CloseCommand extends BaseCommand { ) return interaction .reply({ - content: this.client.locales.ticketOnlyClosableByStaff, + content: this.client.locales.getValue("ticketOnlyClosableByStaff"), ephemeral: true, }) .catch((e) => console.log(e)); diff --git a/src/commands/rename.ts b/src/commands/rename.ts index 674ef497..8a09df1a 100644 --- a/src/commands/rename.ts +++ b/src/commands/rename.ts @@ -36,14 +36,14 @@ export default class RenameCommand extends BaseCommand { if (!(interaction.member as GuildMember | null)?.roles.cache.some((r) => this.client.config.rolesWhoHaveAccessToTheTickets.includes(r.id))) return interaction .reply({ - content: this.client.locales.ticketOnlyRenamableByStaff, + content: this.client.locales.getValue("ticketOnlyRenamableByStaff"), ephemeral: true, }) .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: this.client.locales.ticketRenamed.replace("NEWNAME", (interaction.channel as TextChannel | null)?.toString() ?? "Unknown"), ephemeral: false }) + .reply({ content: this.client.locales.getValue("ticketRenamed").replace("NEWNAME", (interaction.channel as TextChannel | null)?.toString() ?? "Unknown"), ephemeral: false }) .catch((e) => console.log(e)); } } diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 3b5ac985..bb3425e5 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -50,7 +50,7 @@ export default class InteractionCreateEvent extends BaseEvent { const tCount = this.client.config.ticketTypes.length; if(tCount === 0 || tCount > 25) { - await interaction.followUp({content: this.client.locales.invalidConfig, ephemeral: true}); + await interaction.followUp({content: this.client.locales.getValue("invalidConfig"), ephemeral: true}); throw new Error("ticketTypes either has nothing or exceeded 25 entries. Please check the config and restart the bot"); } @@ -74,7 +74,7 @@ export default class InteractionCreateEvent extends BaseEvent { if (ticketsOpened >= this.client.config.maxTicketOpened) { interaction .editReply({ - content: this.client.locales.ticketLimitReached.replace("TICKETLIMIT", this.client.config.maxTicketOpened.toString()) + content: this.client.locales.getValue("ticketLimitReached").replace("TICKETLIMIT", this.client.config.maxTicketOpened.toString()) }) .catch((e) => console.log(e)); return; @@ -110,7 +110,7 @@ export default class InteractionCreateEvent extends BaseEvent { if (options.length <= 0) { interaction.editReply({ - content: this.client.locales.noTickets + content: this.client.locales.getValue("noTickets") }); return; } @@ -118,7 +118,7 @@ export default class InteractionCreateEvent extends BaseEvent { const row = new ActionRowBuilder().addComponents( new StringSelectMenuBuilder() .setCustomId("selectTicketType") - .setPlaceholder(this.client.locales.other.selectTicketTypePlaceholder) + .setPlaceholder(this.client.locales.getSubValue("other", "selectTicketTypePlaceholder")) .setMaxValues(1) .addOptions(options) ); @@ -136,7 +136,7 @@ export default class InteractionCreateEvent extends BaseEvent { if (interaction.customId === "close") { await interaction.deferReply({ ephemeral: true }).catch((e) => console.log(e)); - close(interaction, this.client, this.client.locales.other.noReasonGiven); + close(interaction, this.client, this.client.locales.getSubValue("other", "noReasonGiven")); } if (interaction.customId === "close_askReason") { @@ -157,7 +157,7 @@ export default class InteractionCreateEvent extends BaseEvent { if (ticketsOpened >= this.client.config.maxTicketOpened) { interaction .reply({ - content: this.client.locales.ticketLimitReached.replace("TICKETLIMIT", this.client.config.maxTicketOpened.toString()), + content: this.client.locales.getValue("ticketLimitReached").replace("TICKETLIMIT", this.client.config.maxTicketOpened.toString()), ephemeral: true, }) .catch((e) => console.log(e)); @@ -173,7 +173,7 @@ export default class InteractionCreateEvent extends BaseEvent { if(qCount === 0 || qCount > 5) throw new Error(`${ticketType.codeName} has either no questions or exceeded 5 questions. Check your config and restart the bot`); - const modal = new ModalBuilder().setCustomId("askReason").setTitle(this.client.locales.modals.reasonTicketOpen.title); + const modal = new ModalBuilder().setCustomId("askReason").setTitle(this.client.locales.getSubValue("modals", "reasonTicketOpen", "title")); for (const question of ticketType.questions) { const index = ticketType.questions.indexOf(question); const input = new TextInputBuilder() @@ -189,7 +189,7 @@ export default class InteractionCreateEvent extends BaseEvent { await interaction.showModal(modal).catch((e) => console.log(e)); } else { - createTicket(interaction, this.client, ticketType, this.client.locales.other.noReasonGiven); + createTicket(interaction, this.client, ticketType, this.client.locales.getSubValue("other", "noReasonGiven")); } } diff --git a/src/events/ready.ts b/src/events/ready.ts index 01c16950..f50fc0ed 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -61,7 +61,7 @@ export default class ReadyEvent extends BaseEvent { process.exit(0); } - const embedDat = {...this.client.locales.embeds.openTicket}; + const embedDat = {...this.client.rawLocales.embeds.openTicket}; const footer = embedDat.footer.text.replace("ticket.pm", ""); // 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 @@ -73,7 +73,7 @@ export default class ReadyEvent extends BaseEvent { .setColor(embedDat.color ?? this.client.config.mainColor); const row = new ActionRowBuilder().addComponents( - new ButtonBuilder().setCustomId("openTicket").setLabel(this.client.locales.other.openTicketButtonMSG).setStyle(ButtonStyle.Primary) + new ButtonBuilder().setCustomId("openTicket").setLabel(this.client.locales.getSubValue("other", "openTicketButtonMSG")).setStyle(ButtonStyle.Primary) ); try { diff --git a/src/structure/ExtendedClient.ts b/src/structure/ExtendedClient.ts index 1924ca91..df3d7926 100644 --- a/src/structure/ExtendedClient.ts +++ b/src/structure/ExtendedClient.ts @@ -1,5 +1,5 @@ import {Client, ClientOptions, Collection, Routes} from "discord.js"; -import {BaseCommand, ConfigType} from "./"; +import {BaseCommand, ConfigType, LocaleType} from "./"; import {PrismaClient} from "@prisma/client"; import fs from "fs-extra"; import path from "node:path"; @@ -13,6 +13,7 @@ export default class ExtendedClient extends Client { public readonly config: ConfigType; public readonly prisma: PrismaClient; public locales: Translation; + public rawLocales: LocaleType; public commands: Collection; constructor(options: ClientOptions, config: ConfigType) { super(options); @@ -20,6 +21,7 @@ export default class ExtendedClient extends Client { this.config = config; this.prisma = new PrismaClient(); this.locales = new Translation(this.config.lang, path.join(__dirname, "../../locales/")); + this.rawLocales = JSON.parse(fs.readFileSync(path.join(__dirname, `../../locales/${this.config.lang}.json`), "utf8")); this.commands = new Collection([ [AddCommand.data.name, new AddCommand(this)], [ClaimCommand.data.name, new ClaimCommand(this)], diff --git a/src/utils/claim.ts b/src/utils/claim.ts index 572d46ab..841085a9 100644 --- a/src/utils/claim.ts +++ b/src/utils/claim.ts @@ -37,7 +37,7 @@ export const claim = async(interaction: ButtonInteraction | CommandInteraction, if (!canClaim) return interaction .reply({ - content: client.locales.ticketOnlyClaimableByStaff, + content: client.locales.getValue("ticketOnlyClaimableByStaff"), ephemeral: true, }) .catch((e) => console.log(e)); @@ -45,7 +45,7 @@ export const claim = async(interaction: ButtonInteraction | CommandInteraction, if (claimed) return interaction .reply({ - content: client.locales.ticketAlreadyClaimed, + content: client.locales.getValue("ticketAlreadyClaimed"), ephemeral: true, }) .catch((e) => console.log(e)); @@ -74,7 +74,7 @@ export const claim = async(interaction: ButtonInteraction | CommandInteraction, 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}>`)}`); + .setDescription(oldEmbed?.description + `\n\n ${client.locales.getSubValue("other", "claimedBy").replace("USER", `<@${interaction.user.id}>`)}`); const row = new ActionRowBuilder(); msg?.components[0].components.map((x) => { @@ -91,7 +91,7 @@ export const claim = async(interaction: ButtonInteraction | CommandInteraction, interaction .reply({ - content: client.locales.ticketClaimedMessage.replace("USER", `<@${interaction.user.id}>`), + content: client.locales.getValue("ticketClaimedMessage").replace("USER", `<@${interaction.user.id}>`), ephemeral: false, }) .catch((e) => console.log(e)); diff --git a/src/utils/close.ts b/src/utils/close.ts index aac71756..3cf63e14 100644 --- a/src/utils/close.ts +++ b/src/utils/close.ts @@ -40,7 +40,7 @@ type ticketType = { } export async function close(interaction: ButtonInteraction | CommandInteraction | ModalSubmitInteraction, client: ExtendedClient, reason?: string) { - if (!client.config.closeOption.createTranscript) domain = client.locales.other.unavailable; + if (!client.config.closeOption.createTranscript) domain = client.locales.getSubValue("other","unavailable"); const ticket = await client.prisma.tickets.findUnique({ where: { @@ -56,14 +56,14 @@ export async function close(interaction: ButtonInteraction | CommandInteraction ) return interaction .editReply({ - content: client.locales.ticketOnlyClosableByStaff + content: client.locales.getValue("ticketOnlyClosableByStaff") }) .catch((e) => console.log(e)); if (ticketClosed) return interaction .editReply({ - content: client.locales.ticketAlreadyClosed + content: client.locales.getValue("ticketAlreadyClosed") }) .catch((e) => console.log(e)); @@ -99,7 +99,7 @@ export async function close(interaction: ButtonInteraction | CommandInteraction interaction .editReply({ - content: client.locales.ticketCreatingTranscript + content: client.locales.getValue("ticketCreatingTranscript") }) .catch((e) => console.log(e)); async function _close(id: string, ticket: ticketType) { @@ -125,9 +125,9 @@ export async function close(interaction: ButtonInteraction | CommandInteraction .catch((e) => console.log(e)); interaction.channel?.send({ - content: client.locales.ticketTranscriptCreated.replace( + content: client.locales.getValue("ticketTranscriptCreated").replace( "TRANSCRIPTURL", - domain === client.locales.other.unavailable ? client.locales.other.unavailable : `<${domain}${id}>` + domain === client.locales.getSubValue("other", "unavailable") ? client.locales.getSubValue("other", "unavailable") : `<${domain}${id}>` ) }).catch((e) => console.log(e)); @@ -136,7 +136,7 @@ export async function close(interaction: ButtonInteraction | CommandInteraction closedby: interaction.user.id, closedat: Date.now(), closereason: reason, - transcript: domain === client.locales.other.unavailable ? client.locales.other.unavailable : `${domain}${id}` + transcript: domain === client.locales.getSubValue("other", "unavailable") ? client.locales.getSubValue("other", "unavailable") : `${domain}${id}` }, where: { channelid: interaction.channel?.id @@ -144,15 +144,15 @@ export async function close(interaction: ButtonInteraction | CommandInteraction }); const row = new ActionRowBuilder().addComponents( - new ButtonBuilder().setCustomId("deleteTicket").setLabel(client.locales.other.deleteTicketButtonMSG).setStyle(ButtonStyle.Danger) + new ButtonBuilder().setCustomId("deleteTicket").setLabel(client.locales.getSubValue("other", "deleteTicketButtonMSG")).setStyle(ButtonStyle.Danger) ); - const lEmbed = client.locales.embeds; + const lEmbed = client.rawLocales.embeds; interaction.channel?.send({ embeds: [ JSON.parse( JSON.stringify(lEmbed.ticketClosed) .replace("TICKETCOUNT", ticket.id.toString()) - .replace("REASON", (ticket.closereason ?? client.locales.other.noReasonGiven).replace(/[\n\r]/g, "\\n")) + .replace("REASON", (ticket.closereason ?? client.locales.getSubValue("other", "noReasonGiven")).replace(/[\n\r]/g, "\\n")) .replace("CLOSERNAME", interaction.user.tag) ) ], @@ -169,10 +169,10 @@ export async function close(interaction: ButtonInteraction | CommandInteraction }) .setColor(lEmbed.ticketClosedDM.color ?? client.config.mainColor) .setDescription( - client.locales.embeds.ticketClosedDM.description + client.locales.getSubValue("embeds", "ticketClosedDM", "description") .replace("TICKETCOUNT", ticket.id.toString()) .replace("TRANSCRIPTURL", `[\`${domain}${id}\`](${domain}${id})`) - .replace("REASON", ticket.closereason ?? client.locales.other.noReasonGiven) + .replace("REASON", ticket.closereason ?? client.locales.getSubValue("other", "noReasonGiven")) .replace("CLOSERNAME", interaction.user.tag) ) .setFooter({ diff --git a/src/utils/close_askReason.ts b/src/utils/close_askReason.ts index 19b9e4e9..f3dff112 100644 --- a/src/utils/close_askReason.ts +++ b/src/utils/close_askReason.ts @@ -24,18 +24,18 @@ export const closeAskReason = async(interaction: CommandInteraction | ButtonInte ) return interaction .reply({ - content: client.locales.ticketOnlyClosableByStaff, + content: client.locales.getValue("ticketOnlyClosableByStaff"), ephemeral: true, }) .catch((e) => console.log(e)); - const modal = new ModalBuilder().setCustomId("askReasonClose").setTitle(client.locales.modals.reasonTicketClose.title); + const modal = new ModalBuilder().setCustomId("askReasonClose").setTitle(client.locales.getSubValue("modals", "reasonTicketClose", "title")); const input = new TextInputBuilder() .setCustomId("reason") - .setLabel(client.locales.modals.reasonTicketClose.label) + .setLabel(client.locales.getSubValue("modals","reasonTicketClose", "label")) .setStyle(TextInputStyle.Paragraph) - .setPlaceholder(client.locales.modals.reasonTicketClose.placeholder) + .setPlaceholder(client.locales.getSubValue("modals", "reasonTicketClose", "placeholder")) .setMaxLength(256); const firstActionRow = new ActionRowBuilder().addComponents(input); diff --git a/src/utils/createTicket.ts b/src/utils/createTicket.ts index 0c104f26..880e8dcf 100644 --- a/src/utils/createTicket.ts +++ b/src/utils/createTicket.ts @@ -106,7 +106,7 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo .catch((e) => console.log(e)); }); } - const lEmbeds = client.locales.embeds; + const lEmbeds = client.rawLocales.embeds; const footer = lEmbeds.ticketOpened.footer.text.replace("ticket.pm", ""); if(ticketType.color?.toString().trim() === "") ticketType.color = undefined; const ticketOpenedEmbed = new EmbedBuilder({ @@ -160,16 +160,16 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo row.addComponents( new ButtonBuilder() .setCustomId("close_askReason") - .setLabel(client.locales.buttons.close.label) - .setEmoji(client.locales.buttons.close.emoji) + .setLabel(client.rawLocales.buttons.close.label) + .setEmoji(client.rawLocales.buttons.close.emoji) .setStyle(ButtonStyle.Danger) ); } else { row.addComponents( new ButtonBuilder() .setCustomId("close") - .setLabel(client.locales.buttons.close.label) - .setEmoji(client.locales.buttons.close.emoji) + .setLabel(client.rawLocales.buttons.close.label) + .setEmoji(client.rawLocales.buttons.close.emoji) .setStyle(ButtonStyle.Danger) ); } @@ -179,8 +179,8 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo row.addComponents( new ButtonBuilder() .setCustomId("claim") - .setLabel(client.locales.buttons.claim.label) - .setEmoji(client.locales.buttons.claim.emoji) + .setLabel(client.rawLocales.buttons.claim.label) + .setEmoji(client.rawLocales.buttons.claim.emoji) .setStyle(ButtonStyle.Primary) ); } @@ -213,7 +213,7 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo }); interaction .editReply({ - content: client.locales.ticketOpenedMessage.replace("TICKETCHANNEL", `<#${channel.id}>`), + content: client.locales.getValue("ticketOpenedMessage").replace("TICKETCHANNEL", `<#${channel.id}>`), components: [], }) diff --git a/src/utils/translation.ts b/src/utils/translation.ts index b373c9b2..ed882025 100644 --- a/src/utils/translation.ts +++ b/src/utils/translation.ts @@ -22,9 +22,9 @@ export class Translation { if(!fs.existsSync(fullDir)) throw new TranslationError("Translation file not found, check your config to verify if the name is correct or not"); - this.primaryData = JSON.parse(fullDir); + this.primaryData = JSON.parse(fs.readFileSync(fullDir, "utf8")); if(optName !== "main") - this.backupData = JSON.parse(path.join(dir, "main.json")); + this.backupData = JSON.parse(fs.readFileSync(path.join(dir, "main.json"), "utf8")); } /** @@ -45,8 +45,29 @@ export class Translation { // Return the backup translation console.warn(`TRANSLATION: Key '${key}' is missing translation. If you can, please help fill in the translation and make PR for it.`); return backup; - + } + /** + * Get the translation value that isn't on the top of the JSON object + * @param key All the keys leading to the value + * @returns the translation data or throw error if the translation data cannot be found at all + */ + getSubValue(...keys: string[]): string { + // Check the primary value first + let main: {[k: string]: string | undefined} | string | undefined = this.primaryData; + let bkup: {[k: string]: string | undefined} | string | undefined = this.backupData; + for(const key of keys) { + if(typeof(main) === "object") + main = main[key]; + if(this.backupData && typeof(bkup) === "object") + bkup = bkup[key]; + } + if(typeof(main) === "string") return main; + if(typeof(bkup) !== "string") + throw new TranslationError(`TRANSLATION: Key '${keys.join(".")}' failed to pull backup translation. This indiciates this key data does not exist at all.`); + console.warn(`TRANSLATION: Key '${keys.join(".")}' is missign translation. If you can, please help fill in the translation and make PR for it.`); + return bkup; + } } From 353a3705a2fcc359c931413c64517ce2dd7ba7a6 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Tue, 18 Jul 2023 16:50:32 -0400 Subject: [PATCH 08/12] feat: Remove rawLocales --- src/events/ready.ts | 13 ++++++------- src/structure/ExtendedClient.ts | 4 +--- src/utils/close.ts | 13 ++++++------- src/utils/createTicket.ts | 23 +++++++++++------------ 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/src/events/ready.ts b/src/events/ready.ts index f50fc0ed..e9258a64 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -2,7 +2,7 @@ import readline from "readline"; import axios from "axios"; import {client as WebSocketClient, connection} from "websocket"; -import {ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder, Message} from "discord.js"; +import {ActionRowBuilder, ButtonBuilder, ButtonStyle, ColorResolvable, EmbedBuilder, Message} from "discord.js"; import os from "os"; import {BaseEvent, ExtendedClient, SponsorType} from "../structure"; @@ -60,17 +60,16 @@ export default class ReadyEvent extends BaseEvent { console.error("The channel to open tickets is not a channel!"); process.exit(0); } - - const embedDat = {...this.client.rawLocales.embeds.openTicket}; - const footer = embedDat.footer.text.replace("ticket.pm", ""); + const locale = this.client.locales; + let footer = locale.getSubValue("embeds", "openTicket", "footer", "text").replace("ticket.pm", ""); // 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 + footer = `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 ?? this.client.config.mainColor); + .setColor(locale.getSubValue("embeds", "openTicket", "color") as ColorResolvable ?? this.client.config.mainColor) + .setFooter({text: footer}); const row = new ActionRowBuilder().addComponents( new ButtonBuilder().setCustomId("openTicket").setLabel(this.client.locales.getSubValue("other", "openTicketButtonMSG")).setStyle(ButtonStyle.Primary) diff --git a/src/structure/ExtendedClient.ts b/src/structure/ExtendedClient.ts index df3d7926..1924ca91 100644 --- a/src/structure/ExtendedClient.ts +++ b/src/structure/ExtendedClient.ts @@ -1,5 +1,5 @@ import {Client, ClientOptions, Collection, Routes} from "discord.js"; -import {BaseCommand, ConfigType, LocaleType} from "./"; +import {BaseCommand, ConfigType} from "./"; import {PrismaClient} from "@prisma/client"; import fs from "fs-extra"; import path from "node:path"; @@ -13,7 +13,6 @@ export default class ExtendedClient extends Client { public readonly config: ConfigType; public readonly prisma: PrismaClient; public locales: Translation; - public rawLocales: LocaleType; public commands: Collection; constructor(options: ClientOptions, config: ConfigType) { super(options); @@ -21,7 +20,6 @@ export default class ExtendedClient extends Client { this.config = config; this.prisma = new PrismaClient(); this.locales = new Translation(this.config.lang, path.join(__dirname, "../../locales/")); - this.rawLocales = JSON.parse(fs.readFileSync(path.join(__dirname, `../../locales/${this.config.lang}.json`), "utf8")); this.commands = new Collection([ [AddCommand.data.name, new AddCommand(this)], [ClaimCommand.data.name, new ClaimCommand(this)], diff --git a/src/utils/close.ts b/src/utils/close.ts index 3cf63e14..432fc923 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, ButtonInteraction, ButtonStyle, Collection, CommandInteraction, ComponentType, EmbedBuilder, GuildMember, Message, ModalSubmitInteraction, TextChannel } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, Collection, ColorResolvable, CommandInteraction, ComponentType, EmbedBuilder, GuildMember, Message, ModalSubmitInteraction, TextChannel } from "discord.js"; import { log } from "./logs"; import {ExtendedClient} from "../structure"; let domain = "https://ticket.pm/"; @@ -146,11 +146,11 @@ export async function close(interaction: ButtonInteraction | CommandInteraction const row = new ActionRowBuilder().addComponents( new ButtonBuilder().setCustomId("deleteTicket").setLabel(client.locales.getSubValue("other", "deleteTicketButtonMSG")).setStyle(ButtonStyle.Danger) ); - const lEmbed = client.rawLocales.embeds; + const locale = client.locales; interaction.channel?.send({ embeds: [ JSON.parse( - JSON.stringify(lEmbed.ticketClosed) + JSON.stringify(locale.getSubValue("embeds", "ticketClosed")) .replace("TICKETCOUNT", ticket.id.toString()) .replace("REASON", (ticket.closereason ?? client.locales.getSubValue("other", "noReasonGiven")).replace(/[\n\r]/g, "\\n")) .replace("CLOSERNAME", interaction.user.tag) @@ -162,12 +162,11 @@ export async function close(interaction: ButtonInteraction | CommandInteraction if(!client.config.closeOption.dmUser) return; - const footer = lEmbed.ticketClosedDM.footer.text.replace("ticket.pm", ""); + const footer = locale.getSubValue("embeds", "ticketClosedDM", "footer", "text").replace("ticket.pm", ""); const ticketClosedDMEmbed = new EmbedBuilder({ - ...lEmbed, color: 0, }) - .setColor(lEmbed.ticketClosedDM.color ?? client.config.mainColor) + .setColor(locale.getSubValue("ticketClosedDM", "color") as ColorResolvable ?? client.config.mainColor) .setDescription( client.locales.getSubValue("embeds", "ticketClosedDM", "description") .replace("TICKETCOUNT", ticket.id.toString()) @@ -179,7 +178,7 @@ export async function close(interaction: ButtonInteraction | CommandInteraction // 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: lEmbed.ticketClosedDM.footer.iconUrl + iconURL: locale.getSubValue("embeds", "ticketClosedDM", "footer", "iconUrl") }); client.users.fetch(creator).then((user) => { diff --git a/src/utils/createTicket.ts b/src/utils/createTicket.ts index 880e8dcf..f0b97cad 100644 --- a/src/utils/createTicket.ts +++ b/src/utils/createTicket.ts @@ -25,6 +25,7 @@ limitations under the License. * @param {Object|string} reasons */ export const createTicket = async (interaction: StringSelectMenuInteraction | ModalSubmitInteraction, client: ExtendedClient, ticketType: TicketType, reasons?: Collection | string) => { + const locale = client.locales; // 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)); @@ -106,15 +107,13 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo .catch((e) => console.log(e)); }); } - const lEmbeds = client.rawLocales.embeds; - const footer = lEmbeds.ticketOpened.footer.text.replace("ticket.pm", ""); + const footer = locale.getSubValue("ticketOpened", "footer", "text").replace("ticket.pm", ""); if(ticketType.color?.toString().trim() === "") ticketType.color = undefined; const ticketOpenedEmbed = new EmbedBuilder({ - ...lEmbeds.ticketOpened, color: 0, }) .setColor(ticketType.color ?? client.config.mainColor) - .setTitle(lEmbeds.ticketOpened.title.replace("CATEGORYNAME", ticketType.name)) + .setTitle(locale.getSubValue("ticketOpened", "title").replace("CATEGORYNAME", ticketType.name)) .setDescription( ticketType.customDescription ? ticketType.customDescription @@ -131,7 +130,7 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo .replace("REASON7", reason[6]) .replace("REASON8", reason[7]) .replace("REASON9", reason[8]) - : lEmbeds.ticketOpened.description + : locale.getSubValue("ticketOpened", "description") .replace("CATEGORYNAME", ticketType.name) .replace("USERNAME", interaction.user.username) .replace("USERID", interaction.user.id) @@ -150,7 +149,7 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo // 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 + iconURL: locale.getSubValue("ticketOpened", "footer", "iconUrl") }); const row = new ActionRowBuilder(); @@ -160,16 +159,16 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo row.addComponents( new ButtonBuilder() .setCustomId("close_askReason") - .setLabel(client.rawLocales.buttons.close.label) - .setEmoji(client.rawLocales.buttons.close.emoji) + .setLabel(locale.getSubValue("buttons", "close", "label")) + .setEmoji(locale.getSubValue("buttons", "close", "emoji")) .setStyle(ButtonStyle.Danger) ); } else { row.addComponents( new ButtonBuilder() .setCustomId("close") - .setLabel(client.rawLocales.buttons.close.label) - .setEmoji(client.rawLocales.buttons.close.emoji) + .setLabel(locale.getSubValue("buttons", "close", "emoji")) + .setEmoji(locale.getSubValue("buttons", "close", "emoji")) .setStyle(ButtonStyle.Danger) ); } @@ -179,8 +178,8 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo row.addComponents( new ButtonBuilder() .setCustomId("claim") - .setLabel(client.rawLocales.buttons.claim.label) - .setEmoji(client.rawLocales.buttons.claim.emoji) + .setLabel(locale.getSubValue("buttons", "claim", "label")) + .setEmoji(locale.getSubValue("buttons", "claim", "emoji")) .setStyle(ButtonStyle.Primary) ); } From 487af25a815bb73b4a7def953ba86b66c3285a62 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Tue, 18 Jul 2023 17:03:30 -0400 Subject: [PATCH 09/12] fixed openTicket issues --- src/events/ready.ts | 4 +++- src/utils/translation.ts | 14 +++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/events/ready.ts b/src/events/ready.ts index e9258a64..65583b4c 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -68,7 +68,9 @@ export default class ReadyEvent extends BaseEvent { const embed = new EmbedBuilder({ color: 0, }) - .setColor(locale.getSubValue("embeds", "openTicket", "color") as ColorResolvable ?? this.client.config.mainColor) + .setTitle(locale.getSubValue("embeds", "openTicket", "title")) + .setDescription(locale.getSubValue("embeds", "openTicket", "description")) + .setColor(locale.getNoErrorSubValue("embeds", "openTicket", "color") as ColorResolvable | undefined ?? this.client.config.mainColor) .setFooter({text: footer}); const row = new ActionRowBuilder().addComponents( diff --git a/src/utils/translation.ts b/src/utils/translation.ts index ed882025..8197fdd0 100644 --- a/src/utils/translation.ts +++ b/src/utils/translation.ts @@ -46,6 +46,7 @@ export class Translation { console.warn(`TRANSLATION: Key '${key}' is missing translation. If you can, please help fill in the translation and make PR for it.`); return backup; } + /** * Get the translation value that isn't on the top of the JSON object * @param key All the keys leading to the value @@ -67,7 +68,18 @@ export class Translation { throw new TranslationError(`TRANSLATION: Key '${keys.join(".")}' failed to pull backup translation. This indiciates this key data does not exist at all.`); console.warn(`TRANSLATION: Key '${keys.join(".")}' is missign translation. If you can, please help fill in the translation and make PR for it.`); return bkup; - + } + /** + * Used for translation keys that can be empty + * @param keys All the keys leading to the value + * @returns the translation data or undefined if the translation data cannot be found + */ + getNoErrorSubValue(...keys: string[]): string | undefined { + try { + return this.getSubValue(...keys); + } catch(ex) { + return; + } } } From 5bb1cb3205e25b738558fc8a0e6a16653432c950 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Tue, 18 Jul 2023 17:04:19 -0400 Subject: [PATCH 10/12] Revert: Using tab as indent --- .eslintrc.json | 2 +- src/commands/add.ts | 124 ++++---- src/commands/claim.ts | 18 +- src/commands/close.ts | 46 +-- src/commands/index.ts | 10 +- src/commands/remove.ts | 76 ++--- src/commands/rename.ts | 50 ++-- src/events/index.ts | 4 +- src/events/interactionCreate.ts | 442 ++++++++++++++-------------- src/events/ready.ts | 494 ++++++++++++++++---------------- src/index.ts | 40 +-- src/structure/BaseCommand.ts | 10 +- src/structure/BaseEvent.ts | 8 +- src/structure/ExtendedClient.ts | 108 +++---- src/structure/index.ts | 6 +- src/utils/claim.ts | 102 +++---- src/utils/close.ts | 384 ++++++++++++------------- src/utils/close_askReason.ts | 44 +-- src/utils/createTicket.ts | 392 ++++++++++++------------- src/utils/delete.ts | 38 +-- src/utils/logs.ts | 218 +++++++------- src/utils/translation.ts | 110 +++---- 22 files changed, 1363 insertions(+), 1363 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 0bda9762..35b11843 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -16,7 +16,7 @@ "no-console": "off", "no-undef": "warn", "no-constant-condition": "warn", - "indent": ["error", 4], + "indent": ["error", "tab"], "semi": ["error", "always"], "quotes": [2, "double"], "semi-style": ["error", "last"], diff --git a/src/commands/add.ts b/src/commands/add.ts index 4157394f..7990396e 100644 --- a/src/commands/add.ts +++ b/src/commands/add.ts @@ -19,68 +19,68 @@ limitations under the License. */ export default class AddCommand extends BaseCommand { - public static data: SlashCommandBuilder = new SlashCommandBuilder() - .setName("add") - .setDescription("Add someone to the ticket") - .addUserOption((input) => input.setName("user").setDescription("The user to add").setRequired(true)); - constructor(client: ExtendedClient) { - super(client); - } - - async execute(interaction: CommandInteraction) { - - const added = interaction.options.getUser("user", true); - const ticket = await this.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)); - - 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 (invited.length >= 25) - return interaction.reply({ content: "You can't add more than 25 users", ephemeral: true }).catch((e) => console.log(e)); - - invited.push(added.id); - await this.client.prisma.tickets.update({ - data: { - invited: JSON.stringify(invited) - }, - where: { - channelid: interaction.channel?.id - } - }); - - await (interaction.channel as TextChannel | null)?.permissionOverwrites - .edit(added, { - SendMessages: true, - AddReactions: true, - ReadMessageHistory: true, - AttachFiles: true, - ViewChannel: true, - }) - .catch((e) => console.log(e)); - - interaction.reply({ content: `> Added <@${added.id}> to the ticket` }).catch((e) => console.log(e)); - - log( - { - LogType: "userAdded", - user: interaction.user, - ticketId: ticket.id.toString(), - ticketChannelId: interaction.channel?.id, - target: added, - }, - this.client - ); - } + public static data: SlashCommandBuilder = new SlashCommandBuilder() + .setName("add") + .setDescription("Add someone to the ticket") + .addUserOption((input) => input.setName("user").setDescription("The user to add").setRequired(true)); + constructor(client: ExtendedClient) { + super(client); + } + + async execute(interaction: CommandInteraction) { + + const added = interaction.options.getUser("user", true); + const ticket = await this.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)); + + 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 (invited.length >= 25) + return interaction.reply({ content: "You can't add more than 25 users", ephemeral: true }).catch((e) => console.log(e)); + + invited.push(added.id); + await this.client.prisma.tickets.update({ + data: { + invited: JSON.stringify(invited) + }, + where: { + channelid: interaction.channel?.id + } + }); + + await (interaction.channel as TextChannel | null)?.permissionOverwrites + .edit(added, { + SendMessages: true, + AddReactions: true, + ReadMessageHistory: true, + AttachFiles: true, + ViewChannel: true, + }) + .catch((e) => console.log(e)); + + interaction.reply({ content: `> Added <@${added.id}> to the ticket` }).catch((e) => console.log(e)); + + log( + { + LogType: "userAdded", + user: interaction.user, + ticketId: ticket.id.toString(), + ticketChannelId: interaction.channel?.id, + target: added, + }, + this.client + ); + } } /* diff --git a/src/commands/claim.ts b/src/commands/claim.ts index d3e86a97..491519c7 100644 --- a/src/commands/claim.ts +++ b/src/commands/claim.ts @@ -19,15 +19,15 @@ limitations under the License. */ export default class ClaimCommand extends BaseCommand { - public static data: SlashCommandBuilder = new SlashCommandBuilder() - .setName("claim").setDescription("Set the ticket as claimed."); - constructor(client: ExtendedClient) { - super(client); - } - - async execute(interaction: CommandInteraction) { - return claim(interaction, this.client); - } + public static data: SlashCommandBuilder = new SlashCommandBuilder() + .setName("claim").setDescription("Set the ticket as claimed."); + constructor(client: ExtendedClient) { + super(client); + } + + async execute(interaction: CommandInteraction) { + return claim(interaction, this.client); + } } /* diff --git a/src/commands/close.ts b/src/commands/close.ts index 8d00ab9f..10125187 100644 --- a/src/commands/close.ts +++ b/src/commands/close.ts @@ -20,30 +20,30 @@ limitations under the License. */ export default class CloseCommand extends BaseCommand { - public static data: SlashCommandBuilder = new SlashCommandBuilder() - .setName("close").setDescription("Close the ticket"); - constructor(client: ExtendedClient) { - super(client); - } - - async execute(interaction: CommandInteraction) { - if ( - this.client.config.closeOption.whoCanCloseTicket === "STAFFONLY" && + public static data: SlashCommandBuilder = new SlashCommandBuilder() + .setName("close").setDescription("Close the ticket"); + constructor(client: ExtendedClient) { + super(client); + } + + async execute(interaction: CommandInteraction) { + if ( + this.client.config.closeOption.whoCanCloseTicket === "STAFFONLY" && !(interaction.member as GuildMember | null)?.roles.cache.some((r) => this.client.config.rolesWhoHaveAccessToTheTickets.includes(r.id)) - ) - return interaction - .reply({ - content: this.client.locales.getValue("ticketOnlyClosableByStaff"), - ephemeral: true, - }) - .catch((e) => console.log(e)); - - if (this.client.config.closeOption.askReason) { - closeAskReason(interaction, this.client); - } else { - await interaction.deferReply().catch((e) => console.log(e)); - close(interaction, this.client); - } } + ) + return interaction + .reply({ + content: this.client.locales.getValue("ticketOnlyClosableByStaff"), + ephemeral: true, + }) + .catch((e) => console.log(e)); + + if (this.client.config.closeOption.askReason) { + closeAskReason(interaction, this.client); + } else { + await interaction.deferReply().catch((e) => console.log(e)); + close(interaction, this.client); + } } } /* diff --git a/src/commands/index.ts b/src/commands/index.ts index f4887e1a..67e0b5f1 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -5,9 +5,9 @@ import RemoveCommand from "./remove"; import RenameCommand from "./rename"; export { - AddCommand, - ClaimCommand, - CloseCommand, - RemoveCommand, - RenameCommand + AddCommand, + ClaimCommand, + CloseCommand, + RemoveCommand, + RenameCommand }; \ No newline at end of file diff --git a/src/commands/remove.ts b/src/commands/remove.ts index bb52e11d..ee9f54c9 100644 --- a/src/commands/remove.ts +++ b/src/commands/remove.ts @@ -18,49 +18,49 @@ limitations under the License. */ export default class RemoveCommand extends BaseCommand { - public static data: SlashCommandBuilder = new SlashCommandBuilder() - .setName("remove").setDescription("Remove someone from the ticket"); - constructor(client: ExtendedClient) { - super(client); - } + public static data: SlashCommandBuilder = new SlashCommandBuilder() + .setName("remove").setDescription("Remove someone from the ticket"); + constructor(client: ExtendedClient) { + super(client); + } - async execute(interaction: CommandInteraction) { - const ticket = await this.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)); + async execute(interaction: CommandInteraction) { + const ticket = await this.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)); - 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)); + 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)); - const addedUsers: User[] = []; - for (let i = 0; i < invited.length; i++) { - addedUsers.push(await this.client.users.fetch(invited[i])); - } + const addedUsers: User[] = []; + for (let i = 0; i < invited.length; i++) { + addedUsers.push(await this.client.users.fetch(invited[i])); + } - const row = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId("removeUser") - .setPlaceholder("Please select a user to remove") - .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, - value: user.id, - }; - }) - ) - ); + const row = new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId("removeUser") + .setPlaceholder("Please select a user to remove") + .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, + value: user.id, + }; + }) + ) + ); - interaction.reply({ components: [row] }).catch((e) => console.log(e)); } + interaction.reply({ components: [row] }).catch((e) => console.log(e)); } } /* diff --git a/src/commands/rename.ts b/src/commands/rename.ts index 8a09df1a..9b7fc6b4 100644 --- a/src/commands/rename.ts +++ b/src/commands/rename.ts @@ -18,33 +18,33 @@ limitations under the License. */ export default class RenameCommand extends BaseCommand { - public static data: SlashCommandBuilder = new SlashCommandBuilder() - .setName("rename") - .setDescription("Rename the ticket") - .addStringOption((option) => option.setName("name").setDescription("The new name of the ticket").setRequired(true)); - constructor(client: ExtendedClient) { - super(client); - } + public static data: SlashCommandBuilder = new SlashCommandBuilder() + .setName("rename") + .setDescription("Rename the ticket") + .addStringOption((option) => option.setName("name").setDescription("The new name of the ticket").setRequired(true)); + constructor(client: ExtendedClient) { + super(client); + } - async execute(interaction: CommandInteraction) { - const ticket = await this.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) => this.client.config.rolesWhoHaveAccessToTheTickets.includes(r.id))) - return interaction - .reply({ - content: this.client.locales.getValue("ticketOnlyRenamableByStaff"), - ephemeral: true, - }) - .catch((e) => console.log(e)); + async execute(interaction: CommandInteraction) { + const ticket = await this.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) => this.client.config.rolesWhoHaveAccessToTheTickets.includes(r.id))) + return interaction + .reply({ + content: this.client.locales.getValue("ticketOnlyRenamableByStaff"), + ephemeral: true, + }) + .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: this.client.locales.getValue("ticketRenamed").replace("NEWNAME", (interaction.channel as TextChannel | null)?.toString() ?? "Unknown"), ephemeral: false }) - .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: this.client.locales.getValue("ticketRenamed").replace("NEWNAME", (interaction.channel as TextChannel | null)?.toString() ?? "Unknown"), ephemeral: false }) + .catch((e) => console.log(e)); } } /* diff --git a/src/events/index.ts b/src/events/index.ts index 8e1f6cf4..225087f9 100644 --- a/src/events/index.ts +++ b/src/events/index.ts @@ -2,6 +2,6 @@ import InteractionCreateEvent from "./interactionCreate"; import ReadyEvent from "./ready"; export { - InteractionCreateEvent, - ReadyEvent + InteractionCreateEvent, + ReadyEvent }; \ No newline at end of file diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index bb3425e5..16578642 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -24,228 +24,228 @@ limitations under the License. */ export default class InteractionCreateEvent extends BaseEvent { - constructor(client: ExtendedClient) { - super(client); - } - - public async execute(interaction: Interaction): Promise { - if (interaction.isChatInputCommand()) { - const command = this.client.commands.get(interaction.commandName); - if (!command) return; - - try { - await command.execute(interaction); - } catch (error) { - console.error(error); - await interaction.reply({ - content: "There was an error while executing this command!", - ephemeral: true - }); - } - } - - if (interaction.isButton()) { - if (interaction.customId === "openTicket") { - await interaction.deferReply({ ephemeral: true }).catch((e) => console.log(e)); - - const tCount = this.client.config.ticketTypes.length; - if(tCount === 0 || tCount > 25) { - await interaction.followUp({content: this.client.locales.getValue("invalidConfig"), ephemeral: true}); - throw new Error("ticketTypes either has nothing or exceeded 25 entries. Please check the config and restart the bot"); - } - - for (const role of this.client.config.rolesWhoCanNotCreateTickets) { - if (role && (interaction.member as GuildMember | null)?.roles.cache.has(role)) { - interaction - .editReply({ - content: "You can't create a ticket because you are blacklisted" - }) - .catch((e) => console.log(e)); - return; - } - } + constructor(client: ExtendedClient) { + super(client); + } + + public async execute(interaction: Interaction): Promise { + if (interaction.isChatInputCommand()) { + const command = this.client.commands.get(interaction.commandName); + if (!command) return; + + try { + await command.execute(interaction); + } catch (error) { + console.error(error); + await interaction.reply({ + content: "There was an error while executing this command!", + ephemeral: true + }); + } + } + + if (interaction.isButton()) { + if (interaction.customId === "openTicket") { + await interaction.deferReply({ ephemeral: true }).catch((e) => console.log(e)); + + const tCount = this.client.config.ticketTypes.length; + if(tCount === 0 || tCount > 25) { + await interaction.followUp({content: this.client.locales.getValue("invalidConfig"), ephemeral: true}); + throw new Error("ticketTypes either has nothing or exceeded 25 entries. Please check the config and restart the bot"); + } + + for (const role of this.client.config.rolesWhoCanNotCreateTickets) { + if (role && (interaction.member as GuildMember | null)?.roles.cache.has(role)) { + interaction + .editReply({ + content: "You can't create a ticket because you are blacklisted" + }) + .catch((e) => console.log(e)); + return; + } + } - // Max Ticket - if (this.client.config.maxTicketOpened > 0) { - const ticketsOpened = (await this.client.prisma.$queryRaw<[{count: bigint}]> - `SELECT COUNT(*) as count FROM tickets WHERE closedby IS NULL AND creator = ${interaction.user.id}`)[0].count; - - // If maxTicketOpened is 0, it means that there is no limit - if (ticketsOpened >= this.client.config.maxTicketOpened) { - interaction - .editReply({ - content: this.client.locales.getValue("ticketLimitReached").replace("TICKETLIMIT", this.client.config.maxTicketOpened.toString()) - }) - .catch((e) => console.log(e)); - return; - } - } - - // Make a select menus of all tickets types - let options: SelectMenuComponentOptionData[] = []; - - for (const x of this.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: SelectMenuComponentOptionData = { - label: x.name, - value: x.codeName, - }; - if (x.description) a.description = x.description; - if (x.emoji) a.emoji = x.emoji; - options.push(a); - } - - for (const x of options) { - const option = this.client.config.ticketTypes.filter((y) => y.codeName === x.value)[0]; - if (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); - } - } - } - } - - if (options.length <= 0) { - interaction.editReply({ - content: this.client.locales.getValue("noTickets") - }); - return; - } - - const row = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId("selectTicketType") - .setPlaceholder(this.client.locales.getSubValue("other", "selectTicketTypePlaceholder")) - .setMaxValues(1) - .addOptions(options) - ); - - interaction - .editReply({ - components: [row], - }) - .catch((e) => console.log(e)); - } - - if (interaction.customId === "claim") { - claim(interaction, this.client); - } - - if (interaction.customId === "close") { - await interaction.deferReply({ ephemeral: true }).catch((e) => console.log(e)); - close(interaction, this.client, this.client.locales.getSubValue("other", "noReasonGiven")); - } - - if (interaction.customId === "close_askReason") { - closeAskReason(interaction, this.client); - } - - if (interaction.customId === "deleteTicket") { - deleteTicket(interaction, this.client); - } - } - - if (interaction.isStringSelectMenu()) { - if (interaction.customId === "selectTicketType") { - if (this.client.config.maxTicketOpened > 0) { - const ticketsOpened = (await this.client.prisma.$queryRaw<[{count: bigint}]> - `SELECT COUNT(*) as count FROM tickets WHERE closedby IS NULL AND creator = ${interaction.user.id}`)[0].count; - // If maxTicketOpened is 0, it means that there is no limit - if (ticketsOpened >= this.client.config.maxTicketOpened) { - interaction - .reply({ - content: this.client.locales.getValue("ticketLimitReached").replace("TICKETLIMIT", this.client.config.maxTicketOpened.toString()), - ephemeral: true, - }) - .catch((e) => console.log(e)); - return; - } - } - - const ticketType = this.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) { - // Sanity Check - const qCount = ticketType.questions.length; - if(qCount === 0 || qCount > 5) - throw new Error(`${ticketType.codeName} has either no questions or exceeded 5 questions. Check your config and restart the bot`); - - const modal = new ModalBuilder().setCustomId("askReason").setTitle(this.client.locales.getSubValue("modals", "reasonTicketOpen", "title")); - for (const question of ticketType.questions) { - const index = ticketType.questions.indexOf(question); - const input = new TextInputBuilder() - .setCustomId(`input_${interaction.values[0]}_${index}`) - .setLabel(question.label) - .setStyle(question.style == "SHORT" ? TextInputStyle.Short : TextInputStyle.Paragraph) - .setPlaceholder(question.placeholder) - .setMaxLength(question.maxLength); - - const firstActionRow = new ActionRowBuilder().addComponents(input); - modal.addComponents(firstActionRow); - } - - await interaction.showModal(modal).catch((e) => console.log(e)); - } else { - createTicket(interaction, this.client, ticketType, this.client.locales.getSubValue("other", "noReasonGiven")); - } - } - - if (interaction.customId === "removeUser") { - const ticket = await this.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)); - - log( - { - LogType: "userRemoved", - user: interaction.user, - ticketId: ticket?.id.toString(), - ticketChannelId: interaction.channel?.id, - target: { - id: value, - }, - }, - this.client - ); - }); - - interaction - .update({ - content: `> Removed ${ - interaction.values.length < 1 ? interaction.values : interaction.values.map((a) => `<@${a}>`).join(", ") - } from the ticket`, - components: [], - }) - .catch((e) => console.log(e)); - } - } - - if (interaction.isModalSubmit()) { - if (interaction.customId === "askReason") { - const type = interaction.fields.fields.first()?.customId.split("_")[1]; - const ticketType = this.client.config.ticketTypes.find((x) => x.codeName === type); - // Using customId until the value can be figured out - if (!ticketType) return console.error(`Ticket type ${interaction.customId} not found!`); - createTicket(interaction, this.client, ticketType, interaction.fields.fields); - } - - if (interaction.customId === "askReasonClose") { - await interaction.deferReply().catch((e) => console.log(e)); - close(interaction, this.client, interaction.fields.fields.first()?.value); - } - } - } + // Max Ticket + if (this.client.config.maxTicketOpened > 0) { + const ticketsOpened = (await this.client.prisma.$queryRaw<[{count: bigint}]> + `SELECT COUNT(*) as count FROM tickets WHERE closedby IS NULL AND creator = ${interaction.user.id}`)[0].count; + + // If maxTicketOpened is 0, it means that there is no limit + if (ticketsOpened >= this.client.config.maxTicketOpened) { + interaction + .editReply({ + content: this.client.locales.getValue("ticketLimitReached").replace("TICKETLIMIT", this.client.config.maxTicketOpened.toString()) + }) + .catch((e) => console.log(e)); + return; + } + } + + // Make a select menus of all tickets types + let options: SelectMenuComponentOptionData[] = []; + + for (const x of this.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: SelectMenuComponentOptionData = { + label: x.name, + value: x.codeName, + }; + if (x.description) a.description = x.description; + if (x.emoji) a.emoji = x.emoji; + options.push(a); + } + + for (const x of options) { + const option = this.client.config.ticketTypes.filter((y) => y.codeName === x.value)[0]; + if (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); + } + } + } + } + + if (options.length <= 0) { + interaction.editReply({ + content: this.client.locales.getValue("noTickets") + }); + return; + } + + const row = new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId("selectTicketType") + .setPlaceholder(this.client.locales.getSubValue("other", "selectTicketTypePlaceholder")) + .setMaxValues(1) + .addOptions(options) + ); + + interaction + .editReply({ + components: [row], + }) + .catch((e) => console.log(e)); + } + + if (interaction.customId === "claim") { + claim(interaction, this.client); + } + + if (interaction.customId === "close") { + await interaction.deferReply({ ephemeral: true }).catch((e) => console.log(e)); + close(interaction, this.client, this.client.locales.getSubValue("other", "noReasonGiven")); + } + + if (interaction.customId === "close_askReason") { + closeAskReason(interaction, this.client); + } + + if (interaction.customId === "deleteTicket") { + deleteTicket(interaction, this.client); + } + } + + if (interaction.isStringSelectMenu()) { + if (interaction.customId === "selectTicketType") { + if (this.client.config.maxTicketOpened > 0) { + const ticketsOpened = (await this.client.prisma.$queryRaw<[{count: bigint}]> + `SELECT COUNT(*) as count FROM tickets WHERE closedby IS NULL AND creator = ${interaction.user.id}`)[0].count; + // If maxTicketOpened is 0, it means that there is no limit + if (ticketsOpened >= this.client.config.maxTicketOpened) { + interaction + .reply({ + content: this.client.locales.getValue("ticketLimitReached").replace("TICKETLIMIT", this.client.config.maxTicketOpened.toString()), + ephemeral: true, + }) + .catch((e) => console.log(e)); + return; + } + } + + const ticketType = this.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) { + // Sanity Check + const qCount = ticketType.questions.length; + if(qCount === 0 || qCount > 5) + throw new Error(`${ticketType.codeName} has either no questions or exceeded 5 questions. Check your config and restart the bot`); + + const modal = new ModalBuilder().setCustomId("askReason").setTitle(this.client.locales.getSubValue("modals", "reasonTicketOpen", "title")); + for (const question of ticketType.questions) { + const index = ticketType.questions.indexOf(question); + const input = new TextInputBuilder() + .setCustomId(`input_${interaction.values[0]}_${index}`) + .setLabel(question.label) + .setStyle(question.style == "SHORT" ? TextInputStyle.Short : TextInputStyle.Paragraph) + .setPlaceholder(question.placeholder) + .setMaxLength(question.maxLength); + + const firstActionRow = new ActionRowBuilder().addComponents(input); + modal.addComponents(firstActionRow); + } + + await interaction.showModal(modal).catch((e) => console.log(e)); + } else { + createTicket(interaction, this.client, ticketType, this.client.locales.getSubValue("other", "noReasonGiven")); + } + } + + if (interaction.customId === "removeUser") { + const ticket = await this.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)); + + log( + { + LogType: "userRemoved", + user: interaction.user, + ticketId: ticket?.id.toString(), + ticketChannelId: interaction.channel?.id, + target: { + id: value, + }, + }, + this.client + ); + }); + + interaction + .update({ + content: `> Removed ${ + interaction.values.length < 1 ? interaction.values : interaction.values.map((a) => `<@${a}>`).join(", ") + } from the ticket`, + components: [], + }) + .catch((e) => console.log(e)); + } + } + + if (interaction.isModalSubmit()) { + if (interaction.customId === "askReason") { + const type = interaction.fields.fields.first()?.customId.split("_")[1]; + const ticketType = this.client.config.ticketTypes.find((x) => x.codeName === type); + // Using customId until the value can be figured out + if (!ticketType) return console.error(`Ticket type ${interaction.customId} not found!`); + createTicket(interaction, this.client, ticketType, interaction.fields.fields); + } + + if (interaction.customId === "askReasonClose") { + await interaction.deferReply().catch((e) => console.log(e)); + close(interaction, this.client, interaction.fields.fields.first()?.value); + } + } + } } /* diff --git a/src/events/ready.ts b/src/events/ready.ts index 65583b4c..dd35fdde 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -23,132 +23,132 @@ limitations under the License. */ export default class ReadyEvent extends BaseEvent { - private connected = false; - constructor(client: ExtendedClient) { - super(client); - } - - public async execute() { - if (!this.client.config.guildId) { - console.log("⚠️⚠️⚠️ Please add the guild id in the config.jsonc file. ⚠️⚠️⚠️"); - process.exit(0); - } - - await this.client.guilds.fetch(this.client.config.guildId); - await this.client.guilds.cache.get(this.client.config.guildId)?.members.fetch(); - if (!this.client.guilds.cache.get(this.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); - } - - const embedMessageId = (await this.client.prisma.config.findUnique({ - where: { - key: "openTicketMessageId", - } - }))?.value; - await this.client.channels.fetch(this.client.config.openTicketChannelId).catch(() => { - console.error("The channel to open tickets is not found!"); - process.exit(0); - }); - const openTicketChannel = await this.client.channels.cache.get(this.client.config.openTicketChannelId); - if (!openTicketChannel) { - console.error("The channel to open tickets is not found!"); - process.exit(0); - } - - if (!openTicketChannel.isTextBased()) { - console.error("The channel to open tickets is not a channel!"); - process.exit(0); - } - const locale = this.client.locales; - let footer = locale.getSubValue("embeds", "openTicket", "footer", "text").replace("ticket.pm", ""); - // 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) - footer = `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({ - color: 0, - }) - .setTitle(locale.getSubValue("embeds", "openTicket", "title")) - .setDescription(locale.getSubValue("embeds", "openTicket", "description")) - .setColor(locale.getNoErrorSubValue("embeds", "openTicket", "color") as ColorResolvable | undefined ?? this.client.config.mainColor) - .setFooter({text: footer}); - - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder().setCustomId("openTicket").setLabel(this.client.locales.getSubValue("other", "openTicketButtonMSG")).setStyle(ButtonStyle.Primary) - ); - - try { - // Fetch Message object and return undefined if not found - const msg = embedMessageId ? await (()=> new Promise((res)=> { - openTicketChannel?.messages?.fetch(embedMessageId) - .then(msg=>res(msg)) - .catch(()=>res(undefined)); - }))() : undefined; + private connected = false; + constructor(client: ExtendedClient) { + super(client); + } + + public async execute() { + if (!this.client.config.guildId) { + console.log("⚠️⚠️⚠️ Please add the guild id in the config.jsonc file. ⚠️⚠️⚠️"); + process.exit(0); + } + + await this.client.guilds.fetch(this.client.config.guildId); + await this.client.guilds.cache.get(this.client.config.guildId)?.members.fetch(); + if (!this.client.guilds.cache.get(this.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); + } + + const embedMessageId = (await this.client.prisma.config.findUnique({ + where: { + key: "openTicketMessageId", + } + }))?.value; + await this.client.channels.fetch(this.client.config.openTicketChannelId).catch(() => { + console.error("The channel to open tickets is not found!"); + process.exit(0); + }); + const openTicketChannel = await this.client.channels.cache.get(this.client.config.openTicketChannelId); + if (!openTicketChannel) { + console.error("The channel to open tickets is not found!"); + process.exit(0); + } + + if (!openTicketChannel.isTextBased()) { + console.error("The channel to open tickets is not a channel!"); + process.exit(0); + } + const locale = this.client.locales; + let footer = locale.getSubValue("embeds", "openTicket", "footer", "text").replace("ticket.pm", ""); + // 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) + footer = `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({ + color: 0, + }) + .setTitle(locale.getSubValue("embeds", "openTicket", "title")) + .setDescription(locale.getSubValue("embeds", "openTicket", "description")) + .setColor(locale.getNoErrorSubValue("embeds", "openTicket", "color") as ColorResolvable | undefined ?? this.client.config.mainColor) + .setFooter({text: footer}); + + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder().setCustomId("openTicket").setLabel(this.client.locales.getSubValue("other", "openTicketButtonMSG")).setStyle(ButtonStyle.Primary) + ); + + try { + // Fetch Message object and return undefined if not found + const msg = embedMessageId ? await (()=> new Promise((res)=> { + openTicketChannel?.messages?.fetch(embedMessageId) + .then(msg=>res(msg)) + .catch(()=>res(undefined)); + }))() : undefined; - if (msg && msg.id) { - msg.edit({ - embeds: [embed], - components: [row] - }); - } else { - const channel = this.client.channels.cache.get(this.client.config.openTicketChannelId); - if(!channel || !channel.isTextBased()) return console.error("Invalid openTicketChannelId"); - channel.send({ - embeds: [embed], - components: [row] - }).then((rMsg) => { - this.client.prisma.config.upsert({ - create: { - key: "openTicketMessageId", - value: rMsg.id - }, - update: { - value: rMsg.id - }, - where: { - key: "openTicketMessageId" - } - }).then(); // I need .then() for it to execute?!?!?? - }); - } - } catch (e) { - console.error(e); - } - - - this.setStatus(); - setInterval(()=>this.setStatus(), 9e5); // 15 minutes - - readline.cursorTo(process.stdout, 0); - process.stdout.write( - `\x1b[0m🚀 The bot is ready! Logged in as \x1b[37;46;1m${this.client.user?.tag}\x1b[0m (\x1b[37;46;1m${this.client.user?.id}\x1b[0m) + if (msg && msg.id) { + msg.edit({ + embeds: [embed], + components: [row] + }); + } else { + const channel = this.client.channels.cache.get(this.client.config.openTicketChannelId); + if(!channel || !channel.isTextBased()) return console.error("Invalid openTicketChannelId"); + channel.send({ + embeds: [embed], + components: [row] + }).then((rMsg) => { + this.client.prisma.config.upsert({ + create: { + key: "openTicketMessageId", + value: rMsg.id + }, + update: { + value: rMsg.id + }, + where: { + key: "openTicketMessageId" + } + }).then(); // I need .then() for it to execute?!?!?? + }); + } + } catch (e) { + console.error(e); + } + + + this.setStatus(); + setInterval(()=>this.setStatus(), 9e5); // 15 minutes + + readline.cursorTo(process.stdout, 0); + process.stdout.write( + `\x1b[0m🚀 The bot is ready! Logged in as \x1b[37;46;1m${this.client.user?.tag}\x1b[0m (\x1b[37;46;1m${this.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⛅ 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(() => {return;}); - if (a) { - const sponsors: SponsorType[] = a.data; - 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`); - } - - - if ((await this.client.prisma.config.findUnique({ - where: { - key: "firstStart", - } - })) === null) { - await this.client.prisma.config.create({ - data: { - key: "firstStart", - value: "true", - } - }); - - if(!this.client.config.minimalTracking) console.warn(` + ); + + const a = await axios.get("https://raw.githubusercontent.com/Sayrix/sponsors/main/sponsors.json").catch(() => {return;}); + if (a) { + const sponsors: SponsorType[] = a.data; + 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`); + } + + + if ((await this.client.prisma.config.findUnique({ + where: { + key: "firstStart", + } + })) === null) { + await this.client.prisma.config.create({ + data: { + key: "firstStart", + value: "true", + } + }); + + if(!this.client.config.minimalTracking) console.warn(` PRIVACY NOTICES ------------------------------- Telemetry is current set to full and the following information are sent to the server anonymously: @@ -164,7 +164,7 @@ export default class ReadyEvent extends BaseEvent { ------------------------------- 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(` + else console.warn(` PRIVACY NOTICES ------------------------------- Minimal tracking has been enabled; the following information are sent anonymously: @@ -172,129 +172,129 @@ export default class ReadyEvent extends BaseEvent { * NodeJS Version ------------------------------- `.replace(/\t/g, "")); - } - - this.connect(); - - this.client.deployCommands(); - } - - private setStatus(): void { - if (this.client.config.status) { - if (!this.client.config.status.enabled) return; - - let type = 0; - switch(this.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 (this.client.config.status.type && this.client.config.status.text) { - // If the user just want to set the status but not the activity - const url = this.client.config.status.url; - this.client.user?.setPresence({ - activities: [{ name: this.client.config.status.text, type: type, url: (url && url.trim() !== "") ? url : undefined }], - status: this.client.config.status.status, - }); - } - this.client.user?.setStatus(this.client.config.status.status); - } - } - - private connect(): void { - if (this.connected) return; - const ws = new WebSocketClient(); - - ws.on("connectFailed", (e) => { - this.connected = false; - setTimeout(()=>this.connect(), Math.random() * 1e4); - console.log(`❌ WebSocket Error: ${e.toString()}`); - }); - - ws.on("connect", (connection) => { - connection.on("error", (e) => { - this.connected = false; - setTimeout(()=>this.connect(), Math.random() * 1e4); - console.log(`❌ WebSocket Error: ${e.toString()}`); - }); - - connection.on("close", (e) => { - this.connected = false; - setTimeout(()=>this.connect(), Math.random() * 1e4); - console.log(`❌ WebSocket Error: ${e.toString()}`); - }); - - this.connected = true; - console.log("✅ Connected to WebSocket server."); - this.telemetry(connection); - - setInterval(() => { - this.telemetry(connection); - }, 120_000); - }); - - ws.connect("wss://ws.ticket.pm", "echo-protocol"); - - } - - private 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: this.client?.user?.tag, - clientId: this.client?.user?.id, - guildId: this.client?.config?.guildId - }; - // Minimal tracking enabled, remove those info from being sent - if(this.client.config.minimalTracking) { - fullInfo = {}; - moreInfo = {}; - } - connection.sendUTF( - JSON.stringify({ - type: "telemetry", - data: { - stats: { - guilds: this.client?.guilds?.cache?.size, - users: this.client?.users?.cache?.size - }, - infos: { - // eslint-disable-next-line @typescript-eslint/no-var-requires - ticketbotVersion: require("../../package.json").version, - nodeVersion: process.version, - ...fullInfo - }, - ...moreInfo - } - }) - ); - } + } + + this.connect(); + + this.client.deployCommands(); + } + + private setStatus(): void { + if (this.client.config.status) { + if (!this.client.config.status.enabled) return; + + let type = 0; + switch(this.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 (this.client.config.status.type && this.client.config.status.text) { + // If the user just want to set the status but not the activity + const url = this.client.config.status.url; + this.client.user?.setPresence({ + activities: [{ name: this.client.config.status.text, type: type, url: (url && url.trim() !== "") ? url : undefined }], + status: this.client.config.status.status, + }); + } + this.client.user?.setStatus(this.client.config.status.status); + } + } + + private connect(): void { + if (this.connected) return; + const ws = new WebSocketClient(); + + ws.on("connectFailed", (e) => { + this.connected = false; + setTimeout(()=>this.connect(), Math.random() * 1e4); + console.log(`❌ WebSocket Error: ${e.toString()}`); + }); + + ws.on("connect", (connection) => { + connection.on("error", (e) => { + this.connected = false; + setTimeout(()=>this.connect(), Math.random() * 1e4); + console.log(`❌ WebSocket Error: ${e.toString()}`); + }); + + connection.on("close", (e) => { + this.connected = false; + setTimeout(()=>this.connect(), Math.random() * 1e4); + console.log(`❌ WebSocket Error: ${e.toString()}`); + }); + + this.connected = true; + console.log("✅ Connected to WebSocket server."); + this.telemetry(connection); + + setInterval(() => { + this.telemetry(connection); + }, 120_000); + }); + + ws.connect("wss://ws.ticket.pm", "echo-protocol"); + + } + + private 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: this.client?.user?.tag, + clientId: this.client?.user?.id, + guildId: this.client?.config?.guildId + }; + // Minimal tracking enabled, remove those info from being sent + if(this.client.config.minimalTracking) { + fullInfo = {}; + moreInfo = {}; + } + connection.sendUTF( + JSON.stringify({ + type: "telemetry", + data: { + stats: { + guilds: this.client?.guilds?.cache?.size, + users: this.client?.users?.cache?.size + }, + infos: { + // eslint-disable-next-line @typescript-eslint/no-var-requires + ticketbotVersion: require("../../package.json").version, + nodeVersion: process.version, + ...fullInfo + }, + ...moreInfo + } + }) + ); + } } /* diff --git a/src/index.ts b/src/index.ts index d0ecbd19..c8dc9862 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,11 +27,11 @@ 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) => { - console.error(reason, promise, a); + console.error(reason, promise, a); }); process.on("uncaughtException", (err: string) => { - console.error(err); + console.error(err); }); process.stdout.write(` @@ -48,36 +48,36 @@ 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("🔄 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)); - // 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] || + 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)); + // 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] || (latest[0] === current[0] && latest[1] > current[1]) || (latest[0] === current[0] && latest[1] === current[1] && latest[2] > current[2]) - ) - console.warn(`🔄 New version available: ${json[0].name}; Current Version: ${current.join(".")}`); - else console.log("🔄 The ticket-bot is 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"); + }); }); const config: ConfigType = jsonc.parse(fs.readFileSync(path.join(__dirname, "/../config/config.jsonc"), "utf8")); const client = new ExtendedClient({ - intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMembers], - presence: { - status: config.status?.status ?? "online" - } + intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMembers], + presence: { + status: config.status?.status ?? "online" + } }, config); // Login the bot const token = process.env["TOKEN"]; if(!token || token.trim() === "") - throw new Error("TOKEN Environment Not Found"); + throw new Error("TOKEN Environment Not Found"); client.login(process.env["TOKEN"]).then(null); /* diff --git a/src/structure/BaseCommand.ts b/src/structure/BaseCommand.ts index a09fa7d8..1ef13c65 100644 --- a/src/structure/BaseCommand.ts +++ b/src/structure/BaseCommand.ts @@ -2,11 +2,11 @@ import {CommandInteraction, InteractionResponse, SlashCommandBuilder} from "disc import {ExtendedClient} from "./"; export default abstract class BaseCommand { - public static data: SlashCommandBuilder; - protected client: ExtendedClient; - protected constructor(client: ExtendedClient) { - this.client = client; - } + public static data: SlashCommandBuilder; + protected client: ExtendedClient; + protected constructor(client: ExtendedClient) { + this.client = client; + } // eslint-disable-next-line no-unused-vars abstract execute(interaction: CommandInteraction): void | Promise>; diff --git a/src/structure/BaseEvent.ts b/src/structure/BaseEvent.ts index f789cdf1..6926dd08 100644 --- a/src/structure/BaseEvent.ts +++ b/src/structure/BaseEvent.ts @@ -2,10 +2,10 @@ import {ExtendedClient} from "./"; import {ClientEvents} from "discord.js"; export default abstract class BaseEvent { - protected readonly client: ExtendedClient; - protected constructor(client: ExtendedClient) { - this.client = client; - } + protected readonly client: ExtendedClient; + protected constructor(client: ExtendedClient) { + this.client = client; + } // eslint-disable-next-line no-unused-vars protected abstract execute(...args: ClientEvents[keyof ClientEvents]): void | Promise; diff --git a/src/structure/ExtendedClient.ts b/src/structure/ExtendedClient.ts index 1924ca91..e8b6cbfe 100644 --- a/src/structure/ExtendedClient.ts +++ b/src/structure/ExtendedClient.ts @@ -10,71 +10,71 @@ import {REST} from "@discordjs/rest"; import {Translation} from "../utils/translation"; export default class ExtendedClient extends Client { - public readonly config: ConfigType; - public readonly prisma: PrismaClient; - public locales: Translation; - public commands: Collection; - constructor(options: ClientOptions, config: ConfigType) { - super(options); + public readonly config: ConfigType; + public readonly prisma: PrismaClient; + public locales: Translation; + public commands: Collection; + constructor(options: ClientOptions, config: ConfigType) { + super(options); - this.config = config; - this.prisma = new PrismaClient(); - this.locales = new Translation(this.config.lang, path.join(__dirname, "../../locales/")); - this.commands = new Collection([ - [AddCommand.data.name, new AddCommand(this)], - [ClaimCommand.data.name, new ClaimCommand(this)], - [CloseCommand.data.name, new CloseCommand(this)], - [RemoveCommand.data.name, new RemoveCommand(this)], - [RenameCommand.data.name, new RenameCommand(this)], - ]); - this.loadEvents(); + this.config = config; + this.prisma = new PrismaClient(); + this.locales = new Translation(this.config.lang, path.join(__dirname, "../../locales/")); + this.commands = new Collection([ + [AddCommand.data.name, new AddCommand(this)], + [ClaimCommand.data.name, new ClaimCommand(this)], + [CloseCommand.data.name, new CloseCommand(this)], + [RemoveCommand.data.name, new RemoveCommand(this)], + [RenameCommand.data.name, new RenameCommand(this)], + ]); + this.loadEvents(); - } + } - public msToHm (ms: number | Date) { + public msToHm (ms: number | Date) { - if(ms instanceof Date) ms = ms.getTime(); + 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)); - const hoursms = ms % (60 * 60 * 1000); - const minutes = Math.floor(hoursms / (60 * 1000)); - const minutesms = ms % (60 * 1000); - const sec = Math.floor(minutesms / 1000); + const days = Math.floor(ms / (24 * 60 * 60 * 1000)); + const daysms = ms % (24 * 60 * 60 * 1000); + const hours = Math.floor(daysms / (60 * 60 * 1000)); + const hoursms = ms % (60 * 60 * 1000); + const minutes = Math.floor(hoursms / (60 * 1000)); + const minutesms = ms % (60 * 1000); + const sec = Math.floor(minutesms / 1000); - let result = "0s"; + let result = "0s"; - if (days > 0) result = `${days}d ${hours}h ${minutes}m ${sec}s`; - if (hours > 0) result = `${hours}h ${minutes}m ${sec}s`; - if (minutes > 0) result = `${minutes}m ${sec}s`; - if (sec > 0) result = `${sec}s`; - return result; + if (days > 0) result = `${days}d ${hours}h ${minutes}m ${sec}s`; + if (hours > 0) result = `${hours}h ${minutes}m ${sec}s`; + if (minutes > 0) result = `${minutes}m ${sec}s`; + if (sec > 0) result = `${sec}s`; + return result; - } + } - private loadEvents () { - this.on("interactionCreate", (interaction) => new InteractionCreateEvent(this).execute(interaction)); - this.on("ready", () => new ReadyEvent(this).execute()); - } + private loadEvents () { + this.on("interactionCreate", (interaction) => new InteractionCreateEvent(this).execute(interaction)); + this.on("ready", () => new ReadyEvent(this).execute()); + } - public deployCommands() { - const commands = [ - AddCommand.data.toJSON(), - ClaimCommand.data.toJSON(), - CloseCommand.data.toJSON(), - RemoveCommand.data.toJSON(), - RenameCommand.data.toJSON() - ]; + public deployCommands() { + const commands = [ + AddCommand.data.toJSON(), + ClaimCommand.data.toJSON(), + CloseCommand.data.toJSON(), + RemoveCommand.data.toJSON(), + RenameCommand.data.toJSON() + ]; - const { 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")); - if(!process.env["TOKEN"]) throw Error("Discord Token Expected, deploy-command"); - const rest = new REST({ version: "10" }).setToken(process.env["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(this.user?.id ?? "", guildId), { body: commands }) - .then(() => console.log("✅ Successfully registered application commands.")) - .catch(console.error); - } + rest + .put(Routes.applicationGuildCommands(this.user?.id ?? "", guildId), { body: commands }) + .then(() => console.log("✅ Successfully registered application commands.")) + .catch(console.error); + } } \ No newline at end of file diff --git a/src/structure/index.ts b/src/structure/index.ts index 37fdb498..cae6ea13 100644 --- a/src/structure/index.ts +++ b/src/structure/index.ts @@ -4,7 +4,7 @@ import ExtendedClient from "./ExtendedClient"; export * from "./types"; export { - BaseCommand, - BaseEvent, - ExtendedClient, + BaseCommand, + BaseEvent, + ExtendedClient, }; \ No newline at end of file diff --git a/src/utils/claim.ts b/src/utils/claim.ts index 841085a9..0b7599d3 100644 --- a/src/utils/claim.ts +++ b/src/utils/claim.ts @@ -19,22 +19,22 @@ import { log } from "./logs"; import {ExtendedClient} from "../structure"; export const claim = async(interaction: ButtonInteraction | CommandInteraction, client: ExtendedClient) => { - let ticket = await client.prisma.tickets.findUnique({ - where: { - channelid: interaction.channel?.id - } - }); - const claimed = ticket?.claimedat && ticket.claimedby; - - if (!ticket) + let 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", 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.getValue("ticketOnlyClaimableByStaff"), @@ -42,7 +42,7 @@ export const claim = async(interaction: ButtonInteraction | CommandInteraction, }) .catch((e) => console.log(e)); - if (claimed) + if (claimed) return interaction .reply({ content: client.locales.getValue("ticketAlreadyClaimed"), @@ -50,44 +50,44 @@ export const claim = async(interaction: ButtonInteraction | CommandInteraction, }) .catch((e) => console.log(e)); - log( + log( { - LogType: "ticketClaim", + LogType: "ticketClaim", user: interaction.user, ticketId: ticket.id.toString(), ticketChannelId: interaction.channel?.id, ticketCreatedAt: ticket.createdat, }, client - ); - - ticket = await client.prisma.tickets.update({ - data: { - claimedby: interaction.user.id, - claimedat: Date.now() - }, - where: { - channelid: interaction.channel?.id, - } - }); - - 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.getSubValue("other", "claimedBy").replace("USER", `<@${interaction.user.id}>`)}`); - - const row = new ActionRowBuilder(); - msg?.components[0].components.map((x) => { - const btnBuilder = new ButtonBuilder(x.data); + ); + + ticket = await client.prisma.tickets.update({ + data: { + claimedby: interaction.user.id, + claimedat: Date.now() + }, + where: { + channelid: interaction.channel?.id, + } + }); + + 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.getSubValue("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); - }); + row.addComponents(btnBuilder); + }); msg?.edit({ content: msg.content, embeds: [newEmbed], components: [row], - }).catch((e) => console.log(e)); + }).catch((e) => console.log(e)); interaction .reply({ @@ -96,25 +96,25 @@ export const claim = async(interaction: ButtonInteraction | CommandInteraction, }) .catch((e) => console.log(e)); - const defaultName = client.config.claimOption.nameWhenClaimed; + const defaultName = client.config.claimOption.nameWhenClaimed; if (defaultName && defaultName.trim() !== "") { - const creatorUser = await client.users.fetch(ticket.creator); - const newName = defaultName - .replaceAll("S_USERNAME", interaction.user.username) - .replaceAll("U_USERNAME", creatorUser.username) - .replaceAll("S_USERID", interaction.user.id) - .replaceAll("U_USERID", creatorUser.id) - .replaceAll("TICKETCOUNT", ticket.id.toString()); + const creatorUser = await client.users.fetch(ticket.creator); + const newName = defaultName + .replaceAll("S_USERNAME", interaction.user.username) + .replaceAll("U_USERNAME", creatorUser.username) + .replaceAll("S_USERID", interaction.user.id) + .replaceAll("U_USERID", creatorUser.id) + .replaceAll("TICKETCOUNT", ticket.id.toString()); await (interaction.channel as TextChannel | null)?.setName(newName).catch((e) => console.log(e)); } - const categoryID = client.config.claimOption.categoryWhenClaimed; - if(categoryID && categoryID.trim() !== "") { - const category = await interaction.guild?.channels.fetch(categoryID); - if(category?.type !== ChannelType.GuildCategory) - return console.error("claim.ts: USER ERROR - Invalid categoryWhenClaimed ID. Channel must be a category."); - await (interaction.channel as TextChannel | null)?.setParent(category); - } + const categoryID = client.config.claimOption.categoryWhenClaimed; + if(categoryID && categoryID.trim() !== "") { + const category = await interaction.guild?.channels.fetch(categoryID); + if(category?.type !== ChannelType.GuildCategory) + return console.error("claim.ts: USER ERROR - Invalid categoryWhenClaimed ID. Channel must be a category."); + await (interaction.channel as TextChannel | null)?.setParent(category); + } }; /* Copyright 2023 Sayrix (github.com/Sayrix) diff --git a/src/utils/close.ts b/src/utils/close.ts index 432fc923..f6ece0ff 100644 --- a/src/utils/close.ts +++ b/src/utils/close.ts @@ -40,201 +40,201 @@ type ticketType = { } export async function close(interaction: ButtonInteraction | CommandInteraction | ModalSubmitInteraction, client: ExtendedClient, reason?: string) { - if (!client.config.closeOption.createTranscript) domain = client.locales.getSubValue("other","unavailable"); - - 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 ( - client.config.closeOption.whoCanCloseTicket === "STAFFONLY" && + if (!client.config.closeOption.createTranscript) domain = client.locales.getSubValue("other","unavailable"); + + 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 ( + client.config.closeOption.whoCanCloseTicket === "STAFFONLY" && !(interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id)) - ) - return interaction - .editReply({ - content: client.locales.getValue("ticketOnlyClosableByStaff") - }) - .catch((e) => console.log(e)); - - if (ticketClosed) - return interaction - .editReply({ - content: client.locales.getValue("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 - ); + ) + return interaction + .editReply({ + content: client.locales.getValue("ticketOnlyClosableByStaff") + }) + .catch((e) => console.log(e)); + + if (ticketClosed) + return interaction + .editReply({ + content: client.locales.getValue("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 + ); - // Normally the user that closes the ticket will get posted here, but we'll do it when the ticket finalizes - - 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)); - invited.forEach(async (user) => { - (interaction.channel as TextChannel | null)?.permissionOverwrites - .edit(user, { - ViewChannel: false - }) - .catch((e) => console.log(e)); - }); - - interaction - .editReply({ - content: client.locales.getValue("ticketCreatingTranscript") - }) - .catch((e) => console.log(e)); - async function _close(id: string, ticket: ticketType) { - if (client.config.closeOption.closeTicketCategoryId) (interaction.channel as TextChannel | null)?.setParent(client.config.closeOption.closeTicketCategoryId).catch((e) => console.log(e)); - - const msg = await interaction.channel?.messages.fetch(ticket.messageid); - const embed = new EmbedBuilder(msg?.embeds[0].data); - - const rowAction = new ActionRowBuilder(); - msg?.components[0]?.components?.map((x) => { - 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, - embeds: [embed], - components: [rowAction] - }) - .catch((e) => console.log(e)); - - interaction.channel?.send({ - content: client.locales.getValue("ticketTranscriptCreated").replace( - "TRANSCRIPTURL", - domain === client.locales.getSubValue("other", "unavailable") ? client.locales.getSubValue("other", "unavailable") : `<${domain}${id}>` - ) - }).catch((e) => console.log(e)); + // Normally the user that closes the ticket will get posted here, but we'll do it when the ticket finalizes + + 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)); + invited.forEach(async (user) => { + (interaction.channel as TextChannel | null)?.permissionOverwrites + .edit(user, { + ViewChannel: false + }) + .catch((e) => console.log(e)); + }); + + interaction + .editReply({ + content: client.locales.getValue("ticketCreatingTranscript") + }) + .catch((e) => console.log(e)); + async function _close(id: string, ticket: ticketType) { + if (client.config.closeOption.closeTicketCategoryId) (interaction.channel as TextChannel | null)?.setParent(client.config.closeOption.closeTicketCategoryId).catch((e) => console.log(e)); + + const msg = await interaction.channel?.messages.fetch(ticket.messageid); + const embed = new EmbedBuilder(msg?.embeds[0].data); + + const rowAction = new ActionRowBuilder(); + msg?.components[0]?.components?.map((x) => { + 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, + embeds: [embed], + components: [rowAction] + }) + .catch((e) => console.log(e)); + + interaction.channel?.send({ + content: client.locales.getValue("ticketTranscriptCreated").replace( + "TRANSCRIPTURL", + domain === client.locales.getSubValue("other", "unavailable") ? client.locales.getSubValue("other", "unavailable") : `<${domain}${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.getSubValue("other", "unavailable") ? client.locales.getSubValue("other", "unavailable") : `${domain}${id}` - }, - where: { - channelid: interaction.channel?.id - } - }); - - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder().setCustomId("deleteTicket").setLabel(client.locales.getSubValue("other", "deleteTicketButtonMSG")).setStyle(ButtonStyle.Danger) - ); - const locale = client.locales; - interaction.channel?.send({ - embeds: [ - JSON.parse( - JSON.stringify(locale.getSubValue("embeds", "ticketClosed")) - .replace("TICKETCOUNT", ticket.id.toString()) - .replace("REASON", (ticket.closereason ?? client.locales.getSubValue("other", "noReasonGiven")).replace(/[\n\r]/g, "\\n")) - .replace("CLOSERNAME", interaction.user.tag) - ) - ], - components: [row] - }) - .catch((e) => console.log(e)); - - - if(!client.config.closeOption.dmUser) return; - const footer = locale.getSubValue("embeds", "ticketClosedDM", "footer", "text").replace("ticket.pm", ""); - const ticketClosedDMEmbed = new EmbedBuilder({ - color: 0, - }) - .setColor(locale.getSubValue("ticketClosedDM", "color") as ColorResolvable ?? client.config.mainColor) - .setDescription( - client.locales.getSubValue("embeds", "ticketClosedDM", "description") - .replace("TICKETCOUNT", ticket.id.toString()) - .replace("TRANSCRIPTURL", `[\`${domain}${id}\`](${domain}${id})`) - .replace("REASON", ticket.closereason ?? client.locales.getSubValue("other", "noReasonGiven")) - .replace("CLOSERNAME", interaction.user.tag) - ) - .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: locale.getSubValue("embeds", "ticketClosedDM", "footer", "iconUrl") - }); - - client.users.fetch(creator).then((user) => { - user - .send({ - embeds: [ticketClosedDMEmbed] - }) - .catch((e) => console.log(e)); - }); - } - - if (!client.config.closeOption.createTranscript) { - _close("", ticket); - return; - } - - async function fetchAll() { - const 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}&uuid=${client.config.uuidType}`, JSON.stringify(compressed), { - headers: { - "Content-Type": "application/json" - } - }) - .catch(console.error); - _close(ts?.data, ticket); - } - }); + ticket = await client.prisma.tickets.update({ + data: { + closedby: interaction.user.id, + closedat: Date.now(), + closereason: reason, + transcript: domain === client.locales.getSubValue("other", "unavailable") ? client.locales.getSubValue("other", "unavailable") : `${domain}${id}` + }, + where: { + channelid: interaction.channel?.id + } + }); + + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder().setCustomId("deleteTicket").setLabel(client.locales.getSubValue("other", "deleteTicketButtonMSG")).setStyle(ButtonStyle.Danger) + ); + const locale = client.locales; + interaction.channel?.send({ + embeds: [ + JSON.parse( + JSON.stringify(locale.getSubValue("embeds", "ticketClosed")) + .replace("TICKETCOUNT", ticket.id.toString()) + .replace("REASON", (ticket.closereason ?? client.locales.getSubValue("other", "noReasonGiven")).replace(/[\n\r]/g, "\\n")) + .replace("CLOSERNAME", interaction.user.tag) + ) + ], + components: [row] + }) + .catch((e) => console.log(e)); + + + if(!client.config.closeOption.dmUser) return; + const footer = locale.getSubValue("embeds", "ticketClosedDM", "footer", "text").replace("ticket.pm", ""); + const ticketClosedDMEmbed = new EmbedBuilder({ + color: 0, + }) + .setColor(locale.getSubValue("ticketClosedDM", "color") as ColorResolvable ?? client.config.mainColor) + .setDescription( + client.locales.getSubValue("embeds", "ticketClosedDM", "description") + .replace("TICKETCOUNT", ticket.id.toString()) + .replace("TRANSCRIPTURL", `[\`${domain}${id}\`](${domain}${id})`) + .replace("REASON", ticket.closereason ?? client.locales.getSubValue("other", "noReasonGiven")) + .replace("CLOSERNAME", interaction.user.tag) + ) + .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: locale.getSubValue("embeds", "ticketClosedDM", "footer", "iconUrl") + }); + + client.users.fetch(creator).then((user) => { + user + .send({ + embeds: [ticketClosedDMEmbed] + }) + .catch((e) => console.log(e)); + }); + } + + if (!client.config.closeOption.createTranscript) { + _close("", ticket); + return; + } + + async function fetchAll() { + const 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}&uuid=${client.config.uuidType}`, JSON.stringify(compressed), { + headers: { + "Content-Type": "application/json" + } + }) + .catch(console.error); + _close(ts?.data, ticket); + } + }); } /* diff --git a/src/utils/close_askReason.ts b/src/utils/close_askReason.ts index f3dff112..5c2db13a 100644 --- a/src/utils/close_askReason.ts +++ b/src/utils/close_askReason.ts @@ -18,29 +18,29 @@ import { ActionRowBuilder, ButtonInteraction, CommandInteraction, GuildMember, M import {ExtendedClient} from "../structure"; export const closeAskReason = async(interaction: CommandInteraction | ButtonInteraction, client: ExtendedClient) => { - if ( - client.config.closeOption.whoCanCloseTicket === "STAFFONLY" && + if ( + client.config.closeOption.whoCanCloseTicket === "STAFFONLY" && !(interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id)) - ) - return interaction - .reply({ - content: client.locales.getValue("ticketOnlyClosableByStaff"), - ephemeral: true, - }) - .catch((e) => console.log(e)); - - const modal = new ModalBuilder().setCustomId("askReasonClose").setTitle(client.locales.getSubValue("modals", "reasonTicketClose", "title")); - - const input = new TextInputBuilder() - .setCustomId("reason") - .setLabel(client.locales.getSubValue("modals","reasonTicketClose", "label")) - .setStyle(TextInputStyle.Paragraph) - .setPlaceholder(client.locales.getSubValue("modals", "reasonTicketClose", "placeholder")) - .setMaxLength(256); - - const firstActionRow = new ActionRowBuilder().addComponents(input); - modal.addComponents(firstActionRow); - await interaction.showModal(modal).catch((e) => console.log(e)); + ) + return interaction + .reply({ + content: client.locales.getValue("ticketOnlyClosableByStaff"), + ephemeral: true, + }) + .catch((e) => console.log(e)); + + const modal = new ModalBuilder().setCustomId("askReasonClose").setTitle(client.locales.getSubValue("modals", "reasonTicketClose", "title")); + + const input = new TextInputBuilder() + .setCustomId("reason") + .setLabel(client.locales.getSubValue("modals","reasonTicketClose", "label")) + .setStyle(TextInputStyle.Paragraph) + .setPlaceholder(client.locales.getSubValue("modals", "reasonTicketClose", "placeholder")) + .setMaxLength(256); + + const firstActionRow = new ActionRowBuilder().addComponents(input); + modal.addComponents(firstActionRow); + await interaction.showModal(modal).catch((e) => console.log(e)); }; /* diff --git a/src/utils/createTicket.ts b/src/utils/createTicket.ts index f0b97cad..3b1ca3a4 100644 --- a/src/utils/createTicket.ts +++ b/src/utils/createTicket.ts @@ -25,201 +25,201 @@ limitations under the License. * @param {Object|string} reasons */ export const createTicket = async (interaction: StringSelectMenuInteraction | ModalSubmitInteraction, client: ExtendedClient, ticketType: TicketType, reasons?: Collection | string) => { - const locale = client.locales; - // 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)); - - const reason: string[] = []; - let allReasons = ""; - - if (typeof reasons === "object") { - reasons.forEach(async (r) => { - reason.push(r.value); - }); - allReasons = reason.map((r, i) => `Question ${i + 1}: ${r}`).join(", "); - } - if(typeof reasons === "string") allReasons = reasons; - - let ticketName = ""; - - let ticketCount = (await client.prisma.$queryRaw<[{count: bigint}]> - `SELECT COUNT(*) as count FROM tickets`)[0].count; - - if (ticketType.ticketNameOption) { - ticketName = ticketType.ticketNameOption - .replace("USERNAME", interaction.user.username) - .replace("USERID", interaction.user.id) - .replace("TICKETCOUNT", ticketCount.toString() ?? "0"); - } else { - ticketName = client.config.ticketNameOption - .replace("USERNAME", interaction.user.username) - .replace("USERID", interaction.user.id) - .replace("TICKETCOUNT", ticketCount.toString() ?? "0"); - } - if(!interaction.guild) return console.error("Interaction createTicket was not executed in a guild"); + const locale = client.locales; + // 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)); + + const reason: string[] = []; + let allReasons = ""; + + if (typeof reasons === "object") { + reasons.forEach(async (r) => { + reason.push(r.value); + }); + allReasons = reason.map((r, i) => `Question ${i + 1}: ${r}`).join(", "); + } + if(typeof reasons === "string") allReasons = reasons; + + let ticketName = ""; + + let ticketCount = (await client.prisma.$queryRaw<[{count: bigint}]> + `SELECT COUNT(*) as count FROM tickets`)[0].count; + + if (ticketType.ticketNameOption) { + ticketName = ticketType.ticketNameOption + .replace("USERNAME", interaction.user.username) + .replace("USERID", interaction.user.id) + .replace("TICKETCOUNT", ticketCount.toString() ?? "0"); + } else { + ticketName = client.config.ticketNameOption + .replace("USERNAME", interaction.user.username) + .replace("USERID", interaction.user.id) + .replace("TICKETCOUNT", ticketCount.toString() ?? "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, - deny: [PermissionFlagsBits.ViewChannel], - }, - ], - }); - - if (!channel) return reject("Couldn't create the ticket channel."); - log( - { - LogType: "ticketCreate", - user: interaction.user, - reason: allReasons, - ticketChannelId: channel.id - }, - client - ); - - // Client.db is set here and incremented ticket count - ticketCount++; - - 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 footer = locale.getSubValue("ticketOpened", "footer", "text").replace("ticket.pm", ""); - if(ticketType.color?.toString().trim() === "") ticketType.color = undefined; - const ticketOpenedEmbed = new EmbedBuilder({ - color: 0, - }) - .setColor(ticketType.color ?? client.config.mainColor) - .setTitle(locale.getSubValue("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", ticketCount.toString() || "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]) - : locale.getSubValue("ticketOpened", "description") - .replace("CATEGORYNAME", ticketType.name) - .replace("USERNAME", interaction.user.username) - .replace("USERID", interaction.user.id) - .replace("TICKETCOUNT", ticketCount.toString() || "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]) - ) - .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: locale.getSubValue("ticketOpened", "footer", "iconUrl") - }); - - const row = new ActionRowBuilder(); - - if (client.config.closeOption.closeButton) { - if (client.config.closeOption.askReason) { - row.addComponents( - new ButtonBuilder() - .setCustomId("close_askReason") - .setLabel(locale.getSubValue("buttons", "close", "label")) - .setEmoji(locale.getSubValue("buttons", "close", "emoji")) - .setStyle(ButtonStyle.Danger) - ); - } else { - row.addComponents( - new ButtonBuilder() - .setCustomId("close") - .setLabel(locale.getSubValue("buttons", "close", "emoji")) - .setEmoji(locale.getSubValue("buttons", "close", "emoji")) - .setStyle(ButtonStyle.Danger) - ); - } - } - - if (client.config.claimOption.claimButton) { - row.addComponents( - new ButtonBuilder() - .setCustomId("claim") - .setLabel(locale.getSubValue("buttons", "claim", "label")) - .setEmoji(locale.getSubValue("buttons", "claim", "emoji")) - .setStyle(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.prisma.tickets.create({ - data: { - category: JSON.stringify(ticketType), - reason: allReasons, - creator: interaction.user.id, - createdat: Date.now(), - channelid: channel.id, - messageid: msg.id - } - }).then(); // Again why tf do I need .then()?!?!? - msg.pin().then(() => { - msg.channel.bulkDelete(1); - }); - interaction - .editReply({ - content: client.locales.getValue("ticketOpenedMessage").replace("TICKETCHANNEL", `<#${channel.id}>`), - components: [], - - }) - .catch((e) => console.log(e)); - - resolve(true); - }) - .catch((e) => console.log(e)); - }); + const channel = await client.guilds.cache.get(client.config.guildId)?.channels.create({ + name: ticketName, + parent: ticketType.categoryId, + permissionOverwrites: [ + { + id: interaction.guild.roles.everyone, + deny: [PermissionFlagsBits.ViewChannel], + }, + ], + }); + + if (!channel) return reject("Couldn't create the ticket channel."); + log( + { + LogType: "ticketCreate", + user: interaction.user, + reason: allReasons, + ticketChannelId: channel.id + }, + client + ); + + // Client.db is set here and incremented ticket count + ticketCount++; + + 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 footer = locale.getSubValue("ticketOpened", "footer", "text").replace("ticket.pm", ""); + if(ticketType.color?.toString().trim() === "") ticketType.color = undefined; + const ticketOpenedEmbed = new EmbedBuilder({ + color: 0, + }) + .setColor(ticketType.color ?? client.config.mainColor) + .setTitle(locale.getSubValue("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", ticketCount.toString() || "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]) + : locale.getSubValue("ticketOpened", "description") + .replace("CATEGORYNAME", ticketType.name) + .replace("USERNAME", interaction.user.username) + .replace("USERID", interaction.user.id) + .replace("TICKETCOUNT", ticketCount.toString() || "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]) + ) + .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: locale.getSubValue("ticketOpened", "footer", "iconUrl") + }); + + const row = new ActionRowBuilder(); + + if (client.config.closeOption.closeButton) { + if (client.config.closeOption.askReason) { + row.addComponents( + new ButtonBuilder() + .setCustomId("close_askReason") + .setLabel(locale.getSubValue("buttons", "close", "label")) + .setEmoji(locale.getSubValue("buttons", "close", "emoji")) + .setStyle(ButtonStyle.Danger) + ); + } else { + row.addComponents( + new ButtonBuilder() + .setCustomId("close") + .setLabel(locale.getSubValue("buttons", "close", "emoji")) + .setEmoji(locale.getSubValue("buttons", "close", "emoji")) + .setStyle(ButtonStyle.Danger) + ); + } + } + + if (client.config.claimOption.claimButton) { + row.addComponents( + new ButtonBuilder() + .setCustomId("claim") + .setLabel(locale.getSubValue("buttons", "claim", "label")) + .setEmoji(locale.getSubValue("buttons", "claim", "emoji")) + .setStyle(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.prisma.tickets.create({ + data: { + category: JSON.stringify(ticketType), + reason: allReasons, + creator: interaction.user.id, + createdat: Date.now(), + channelid: channel.id, + messageid: msg.id + } + }).then(); // Again why tf do I need .then()?!?!? + msg.pin().then(() => { + msg.channel.bulkDelete(1); + }); + interaction + .editReply({ + content: client.locales.getValue("ticketOpenedMessage").replace("TICKETCHANNEL", `<#${channel.id}>`), + components: [], + + }) + .catch((e) => console.log(e)); + + resolve(true); + }) + .catch((e) => console.log(e)); + }); }; diff --git a/src/utils/delete.ts b/src/utils/delete.ts index be49cc6c..74f6a71f 100644 --- a/src/utils/delete.ts +++ b/src/utils/delete.ts @@ -19,25 +19,25 @@ import { log } from "./logs"; import {ExtendedClient} from "../structure"; export const deleteTicket = async (interaction: ButtonInteraction, client: ExtendedClient) => { - 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.transcript ?? undefined, - }, - client - ); - await interaction.deferUpdate(); - interaction.channel?.delete().catch((e) => console.log(e)); + 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.transcript ?? undefined, + }, + client + ); + await interaction.deferUpdate(); + interaction.channel?.delete().catch((e) => console.log(e)); }; /* diff --git a/src/utils/logs.ts b/src/utils/logs.ts index 037d88f2..36a210b4 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -51,121 +51,121 @@ type log = { // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars export const log = async(logs: log, client: ExtendedClient) => { - 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(Number(BigInt(Date.now()) - BigInt(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(Number(BigInt(Date.now()) - BigInt(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(Number(BigInt(Date.now()) - BigInt(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(Number(BigInt(Date.now()) - BigInt(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(Number(BigInt(Date.now()) - BigInt(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(Number(BigInt(Date.now()) - BigInt(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)); + } }; /* diff --git a/src/utils/translation.ts b/src/utils/translation.ts index 8197fdd0..2ec74647 100644 --- a/src/utils/translation.ts +++ b/src/utils/translation.ts @@ -8,85 +8,85 @@ import path from "node:path"; import fs from "fs-extra"; export class Translation { - private primaryData: {[k: string]: string | undefined}; - private backupData?: {[k: string]: string | undefined}; + private primaryData: {[k: string]: string | undefined}; + private backupData?: {[k: string]: string | undefined}; - /** + /** * locale handler module * @param optName The locale file name (w/o extension) * @param dir The directory of the locale files */ - constructor(optName: string, dir?: string) { - dir = dir ?? "./locale"; - const fullDir = path.join(dir, `${optName}.json`); - if(!fs.existsSync(fullDir)) - throw new TranslationError("Translation file not found, check your config to verify if the name is correct or not"); + constructor(optName: string, dir?: string) { + dir = dir ?? "./locale"; + const fullDir = path.join(dir, `${optName}.json`); + if(!fs.existsSync(fullDir)) + throw new TranslationError("Translation file not found, check your config to verify if the name is correct or not"); - this.primaryData = JSON.parse(fs.readFileSync(fullDir, "utf8")); - if(optName !== "main") - this.backupData = JSON.parse(fs.readFileSync(path.join(dir, "main.json"), "utf8")); - } + this.primaryData = JSON.parse(fs.readFileSync(fullDir, "utf8")); + if(optName !== "main") + this.backupData = JSON.parse(fs.readFileSync(path.join(dir, "main.json"), "utf8")); + } - /** + /** * Get the translation value or backup value if it doesn't exist * @param key The object key the translation should pull * @returns the translation data or throw error if the translation data cannot be found at all */ - getValue(key: string): string { - // Try return the data from the main translation file - const main = this.primaryData[key]; - if(main) return main; + getValue(key: string): string { + // Try return the data from the main translation file + const main = this.primaryData[key]; + if(main) return main; - // Pull backup and throw error if it doesn't exist - const backup = this.backupData && this.backupData[key]; - if(!backup) - throw new TranslationError(`TRANSLATION: Key '${key}' failed to pull backup translation. This indiciates this key data does not exist at all.`); + // Pull backup and throw error if it doesn't exist + const backup = this.backupData && this.backupData[key]; + if(!backup) + throw new TranslationError(`TRANSLATION: Key '${key}' failed to pull backup translation. This indiciates this key data does not exist at all.`); - // Return the backup translation - console.warn(`TRANSLATION: Key '${key}' is missing translation. If you can, please help fill in the translation and make PR for it.`); - return backup; - } + // Return the backup translation + console.warn(`TRANSLATION: Key '${key}' is missing translation. If you can, please help fill in the translation and make PR for it.`); + return backup; + } - /** + /** * Get the translation value that isn't on the top of the JSON object * @param key All the keys leading to the value * @returns the translation data or throw error if the translation data cannot be found at all */ - getSubValue(...keys: string[]): string { - // Check the primary value first - let main: {[k: string]: string | undefined} | string | undefined = this.primaryData; - let bkup: {[k: string]: string | undefined} | string | undefined = this.backupData; - for(const key of keys) { - if(typeof(main) === "object") - main = main[key]; - if(this.backupData && typeof(bkup) === "object") - bkup = bkup[key]; - } + getSubValue(...keys: string[]): string { + // Check the primary value first + let main: {[k: string]: string | undefined} | string | undefined = this.primaryData; + let bkup: {[k: string]: string | undefined} | string | undefined = this.backupData; + for(const key of keys) { + if(typeof(main) === "object") + main = main[key]; + if(this.backupData && typeof(bkup) === "object") + bkup = bkup[key]; + } - if(typeof(main) === "string") return main; - if(typeof(bkup) !== "string") - throw new TranslationError(`TRANSLATION: Key '${keys.join(".")}' failed to pull backup translation. This indiciates this key data does not exist at all.`); - console.warn(`TRANSLATION: Key '${keys.join(".")}' is missign translation. If you can, please help fill in the translation and make PR for it.`); - return bkup; - } - /** + if(typeof(main) === "string") return main; + if(typeof(bkup) !== "string") + throw new TranslationError(`TRANSLATION: Key '${keys.join(".")}' failed to pull backup translation. This indiciates this key data does not exist at all.`); + console.warn(`TRANSLATION: Key '${keys.join(".")}' is missign translation. If you can, please help fill in the translation and make PR for it.`); + return bkup; + } + /** * Used for translation keys that can be empty * @param keys All the keys leading to the value * @returns the translation data or undefined if the translation data cannot be found */ - getNoErrorSubValue(...keys: string[]): string | undefined { - try { - return this.getSubValue(...keys); - } catch(ex) { - return; - } - } + getNoErrorSubValue(...keys: string[]): string | undefined { + try { + return this.getSubValue(...keys); + } catch(ex) { + return; + } + } } export class TranslationError { - name: string = "TranslationError"; - message: string; - constructor(msg: string) { - this.message = msg; - } + name: string = "TranslationError"; + message: string; + constructor(msg: string) { + this.message = msg; + } } \ No newline at end of file From ab20fbe48a8b16d8e54bface1edeb1f2e085c4fa Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Tue, 18 Jul 2023 21:49:49 -0400 Subject: [PATCH 11/12] refactor: Fixed some typos --- src/utils/translation.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/translation.ts b/src/utils/translation.ts index 2ec74647..3d218489 100644 --- a/src/utils/translation.ts +++ b/src/utils/translation.ts @@ -40,7 +40,7 @@ export class Translation { // Pull backup and throw error if it doesn't exist const backup = this.backupData && this.backupData[key]; if(!backup) - throw new TranslationError(`TRANSLATION: Key '${key}' failed to pull backup translation. This indiciates this key data does not exist at all.`); + throw new TranslationError(`TRANSLATION: Key '${key}' failed to pull backup translation. This indicates this key data does not exist at all.`); // Return the backup translation console.warn(`TRANSLATION: Key '${key}' is missing translation. If you can, please help fill in the translation and make PR for it.`); @@ -65,8 +65,8 @@ export class Translation { if(typeof(main) === "string") return main; if(typeof(bkup) !== "string") - throw new TranslationError(`TRANSLATION: Key '${keys.join(".")}' failed to pull backup translation. This indiciates this key data does not exist at all.`); - console.warn(`TRANSLATION: Key '${keys.join(".")}' is missign translation. If you can, please help fill in the translation and make PR for it.`); + throw new TranslationError(`TRANSLATION: Key '${keys.join(".")}' failed to pull backup translation. This indicates this key data does not exist at all.`); + console.warn(`TRANSLATION: Key '${keys.join(".")}' is missing translation. If you can, please help fill in the translation and make PR for it.`); return bkup; } /** From 3c8e469d1d1e5c91f1796b41f3d4be5cba130b65 Mon Sep 17 00:00:00 2001 From: zhiyan114 Date: Tue, 18 Jul 2023 21:52:31 -0400 Subject: [PATCH 12/12] fix(translation): getSubValue accounts for numbers in JSON file --- src/utils/translation.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/utils/translation.ts b/src/utils/translation.ts index 3d218489..b353423f 100644 --- a/src/utils/translation.ts +++ b/src/utils/translation.ts @@ -56,6 +56,7 @@ export class Translation { // Check the primary value first let main: {[k: string]: string | undefined} | string | undefined = this.primaryData; let bkup: {[k: string]: string | undefined} | string | undefined = this.backupData; + for(const key of keys) { if(typeof(main) === "object") main = main[key]; @@ -63,8 +64,8 @@ export class Translation { bkup = bkup[key]; } - if(typeof(main) === "string") return main; - if(typeof(bkup) !== "string") + if(typeof(main) === "string" || typeof(main) === "number") return main; + if(typeof(bkup) !== "string" && typeof(bkup) !== "number") throw new TranslationError(`TRANSLATION: Key '${keys.join(".")}' failed to pull backup translation. This indicates this key data does not exist at all.`); console.warn(`TRANSLATION: Key '${keys.join(".")}' is missing translation. If you can, please help fill in the translation and make PR for it.`); return bkup;