diff --git a/.gitignore b/.gitignore index 2d5e4afe..e3b71ab8 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,5 @@ npm-audit.txt # Debug files *.debug.ts *.debug.js + +.vscode/ diff --git a/.nvmrc b/.nvmrc index 8351c193..d925544b 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -14 +16.6 diff --git a/backend/package-lock.json b/backend/package-lock.json index 6edbab28..b44de2a2 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -8,24 +8,23 @@ "name": "@zeppelin/backend", "version": "0.0.1", "dependencies": { - "@types/sharp": "^0.23.1", - "@types/twemoji": "^12.1.0", - "bufferutil": "^4.0.1", + "bufferutil": "^4.0.3", "cors": "^2.8.5", "cross-env": "^5.2.0", "deep-diff": "^1.0.2", + "discord-api-types": "^0.22.0", + "discord.js": "^13.1.0", "dotenv": "^4.0.0", "emoji-regex": "^8.0.0", - "eris": "^0.15.1", - "erlpack": "github:abalabahaha/erlpack", + "erlpack": "github:almeidx/erlpack#f0c535f73817fd914806d6ca26a7730c14e0fb7c", "escape-string-regexp": "^1.0.5", "express": "^4.17.0", "fp-ts": "^2.0.1", "humanize-duration": "^3.15.0", "io-ts": "^2.0.0", "js-yaml": "^3.13.1", - "knub": "^30.0.0-beta.37", - "knub-command-manager": "^8.1.2", + "knub": "file:../../Knub", + "knub-command-manager": "^9.1.0", "last-commit-log": "^2.1.0", "lodash.chunk": "^4.2.0", "lodash.clonedeep": "^4.5.0", @@ -44,13 +43,14 @@ "regexp-worker": "^1.1.0", "safe-regex": "^2.0.2", "seedrandom": "^3.0.1", - "sharp": "^0.23.4", + "sharp": "github:almeidx/sharp#68b4f387ae2ee1ee2dd8f289f5ec5fcf722fd3d3", "strip-combining-marks": "^1.0.0", "tlds": "^1.203.1", "tmp": "0.0.33", "tsconfig-paths": "^3.9.0", "twemoji": "^12.1.4", "typeorm": "^0.2.31", + "utf-8-validate": "^5.0.5", "uuid": "^3.3.2", "yawn-yaml": "github:dragory/yawn-yaml#string-number-fix-build", "zlib-sync": "^0.1.7" @@ -67,12 +67,41 @@ "@types/passport-oauth2": "^1.4.8", "@types/passport-strategy": "^0.2.35", "@types/safe-regex": "^1.1.2", + "@types/sharp": "^0.23.1", "@types/tmp": "0.0.33", + "@types/twemoji": "^12.1.0", "ava": "^3.10.0", "rimraf": "^2.6.2", "source-map-support": "^0.5.16", "tsc-watch": "^4.0.0", - "typescript": "^4.1.3" + "typescript": "^4.3.4" + } + }, + "../../Knub": { + "name": "knub", + "version": "30.0.0-beta.38", + "license": "MIT", + "dependencies": { + "discord-api-types": "^0.22.0", + "discord.js": "^13.1.0", + "knub-command-manager": "^9.1.0", + "ts-essentials": "^6.0.7" + }, + "devDependencies": { + "@types/chai": "^4.2.18", + "@types/mocha": "^7.0.2", + "@types/node": "^14.14.45", + "@typescript-eslint/eslint-plugin": "^4.23.0", + "@typescript-eslint/parser": "^4.23.0", + "chai": "^4.3.4", + "eslint": "^7.2.0", + "husky": "^4.3.8", + "lint-staged": "^10.5.4", + "mocha": "^8.4.0", + "prettier": "^2.3.0", + "shx": "^0.3.3", + "ts-node": "^8.10.2", + "typescript": "^4.2.4" } }, "node_modules/@babel/code-frame": { @@ -132,6 +161,59 @@ "node": ">=0.10.0" } }, + "node_modules/@discordjs/builders": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.5.0.tgz", + "integrity": "sha512-HP5y4Rqw68o61Qv4qM5tVmDbWi4mdTFftqIOGRo33SNPpLJ1Ga3KEIR2ibKofkmsoQhEpLmopD1AZDs3cKpHuw==", + "dependencies": { + "@sindresorhus/is": "^4.0.1", + "discord-api-types": "^0.22.0", + "ow": "^0.27.0", + "ts-mixer": "^6.0.0", + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@discordjs/builders/node_modules/@sindresorhus/is": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", + "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@discordjs/builders/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "node_modules/@discordjs/collection": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.2.1.tgz", + "integrity": "sha512-vhxqzzM8gkomw0TYRF3tgx7SwElzUlXT/Aa41O7mOcyN6wIJfj5JmDWaO5XGKsGSsNx7F3i5oIlrucCCWV1Nog==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@discordjs/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -167,6 +249,15 @@ "node": ">= 8" } }, + "node_modules/@sapphire/async-queue": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.1.4.tgz", + "integrity": "sha512-fFrlF/uWpGOX5djw5Mu2Hnnrunao75WGey0sP0J3jnhmrJ5TAPzHYOmytD5iN/+pMxS+f+u/gezqHa9tPhRHEA==", + "engines": { + "node": ">=14", + "npm": ">=6" + } + }, "node_modules/@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -396,6 +487,7 @@ "version": "0.23.1", "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.23.1.tgz", "integrity": "sha512-iBRM9RjRF9pkIkukk6imlxfaKMRuiRND8L0yYKl5PJu5uLvxuNzp5f0x8aoTG5VX85M8O//BwbttzFVZL1j/FQ==", + "dev": true, "dependencies": { "@types/node": "*" } @@ -409,7 +501,16 @@ "node_modules/@types/twemoji": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/@types/twemoji/-/twemoji-12.1.0.tgz", - "integrity": "sha512-dTHU1ZE83qUlF3oFWrdxKBmOimM+/3o9hzDBszcKjajmNu5G/DjWgQrRNkq+zxeR+zDN030ciAt5qTH+WXBD8A==" + "integrity": "sha512-dTHU1ZE83qUlF3oFWrdxKBmOimM+/3o9hzDBszcKjajmNu5G/DjWgQrRNkq+zxeR+zDN030ciAt5qTH+WXBD8A==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "dependencies": { + "@types/node": "*" + } }, "node_modules/accepts": { "version": "1.3.7", @@ -585,6 +686,11 @@ "node": ">=8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, "node_modules/ava": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/ava/-/ava-3.10.0.tgz", @@ -674,6 +780,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/ava/node_modules/chalk": { @@ -687,6 +796,9 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/ava/node_modules/cliui": { @@ -722,6 +834,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", "dev": true, "dependencies": { "ms": "^2.1.1" @@ -915,6 +1028,12 @@ "node": ">=0.10.0" } }, + "node_modules/ava/node_modules/yargs/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -923,7 +1042,21 @@ "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/base64url": { "version": "3.0.1", @@ -959,17 +1092,24 @@ } }, "node_modules/bl": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-3.0.0.tgz", - "integrity": "sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dependencies": { - "readable-stream": "^3.0.1" + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" } }, + "node_modules/bl/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, "node_modules/bl/node_modules/readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -1022,6 +1162,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/boxen/node_modules/ansi-regex": { @@ -1044,6 +1187,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/boxen/node_modules/chalk": { @@ -1167,6 +1313,20 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -1179,11 +1339,12 @@ "dev": true }, "node_modules/bufferutil": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.1.tgz", - "integrity": "sha512-xowrxvpxojqkagPcWRQVXZl0YXhRhAtBEIq3VoER1NH5Mw1n1o0ojdspp+GS2J//2gCVyrzQDApQ4unGF+QOoA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.3.tgz", + "integrity": "sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==", + "hasInstallScript": true, "dependencies": { - "node-gyp-build": "~3.7.0" + "node-gyp-build": "^4.2.0" } }, "node_modules/bytes": { @@ -1237,7 +1398,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -1287,9 +1447,9 @@ } }, "node_modules/chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, "node_modules/chunkd": { "version": "2.0.1", @@ -1377,6 +1537,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/cli-highlight/node_modules/chalk": { @@ -1389,6 +1552,9 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/cli-highlight/node_modules/color-convert": { @@ -1446,6 +1612,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-truncate/node_modules/ansi-regex": { @@ -1581,12 +1750,12 @@ } }, "node_modules/color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", - "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", "dependencies": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" + "color-convert": "^1.9.3", + "color-string": "^1.6.0" } }, "node_modules/color-convert": { @@ -1603,14 +1772,25 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "node_modules/color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz", + "integrity": "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==", "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/common-path-prefix": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", @@ -1729,7 +1909,12 @@ "version": "3.6.5", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==", - "dev": true + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } }, "node_modules/core-util-is": { "version": "1.0.2", @@ -1919,6 +2104,17 @@ }, "bin": { "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" } }, "node_modules/delegates": { @@ -1962,6 +2158,33 @@ "node": ">=8" } }, + "node_modules/discord-api-types": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.22.0.tgz", + "integrity": "sha512-l8yD/2zRbZItUQpy7ZxBJwaLX/Bs2TGaCthRppk8Sw24LOIWg12t9JEreezPoYD0SQcC2htNNo27kYEpYW/Srg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/discord.js": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.1.0.tgz", + "integrity": "sha512-gxO4CXKdHpqA+WKG+f5RNnd3srTDj5uFJHgOathksDE90YNq/Qijkd2WlMgTTMS6AJoEnHxI7G9eDQHCuZ+xDA==", + "dependencies": { + "@discordjs/builders": "^0.5.0", + "@discordjs/collection": "^0.2.1", + "@discordjs/form-data": "^3.0.1", + "@sapphire/async-queue": "^1.1.4", + "@types/ws": "^7.4.7", + "discord-api-types": "^0.22.0", + "node-fetch": "^2.6.1", + "ws": "^7.5.1" + }, + "engines": { + "node": ">=16.6.0", + "npm": ">=7.0.0" + } + }, "node_modules/dot-prop": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", @@ -2014,6 +2237,9 @@ "dev": true, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, "node_modules/emoji-regex": { @@ -2046,23 +2272,11 @@ "node": ">=4" } }, - "node_modules/eris": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/eris/-/eris-0.15.1.tgz", - "integrity": "sha512-IQ3BPW6OjgFoqjdh+irPOa1jFlkotk+WNu2GQQ7QAQfbzQEPZgn+F+hpOxfMUXPHOZMX4sPKLkVDkMHAssBYhw==", - "dependencies": { - "ws": "^7.2.1" - }, - "engines": { - "node": ">=10.4.0" - }, - "optionalDependencies": { - "opusscript": "^0.0.8", - "tweetnacl": "^1.0.1" - } - }, "node_modules/erlpack": { - "resolved": "git+ssh://git@github.com/abalabahaha/erlpack.git#5d0064f9e106841e1eead711a6451f99b0d289fd", + "version": "0.1.3", + "resolved": "git+ssh://git@github.com/almeidx/erlpack.git#f0c535f73817fd914806d6ca26a7730c14e0fb7c", + "hasInstallScript": true, + "license": "MIT", "dependencies": { "bindings": "^1.5.0", "nan": "^2.14.0" @@ -2249,6 +2463,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/file-uri-to-path": { @@ -2345,21 +2562,10 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dependencies": { + "optionalDependencies": { "graceful-fs": "^4.1.6" } }, - "node_modules/fs-minipass": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.0.0.tgz", - "integrity": "sha512-40Qz+LFXmd9tzYVnnBmZvFfvAADfUA14TXPK1s7IfElJTIZ97rA8w4Kin7Wt5JBrC3ShnnFJO/5vPjPEeJIq9A==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2369,7 +2575,9 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", "dev": true, + "hasInstallScript": true, "optional": true, "os": [ "darwin" @@ -2479,9 +2687,9 @@ } }, "node_modules/glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "dependencies": { "is-glob": "^4.0.1" @@ -2517,6 +2725,9 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/got": { @@ -2597,9 +2808,9 @@ } }, "node_modules/hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "node_modules/http-cache-semantics": { @@ -2642,7 +2853,21 @@ "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/ignore": { "version": "5.1.8", @@ -2720,17 +2945,17 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "node_modules/ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "engines": { - "node": "*" - } + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/io-ts": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.0.1.tgz", - "integrity": "sha512-RezD+WcCfW4VkMkEcQWL/Nmy/nqsWTvTYg7oUmTGzglvSSV2P9h2z1PVeREPFf0GWNzruYleAt1XCMQZSg1xxQ==" + "integrity": "sha512-RezD+WcCfW4VkMkEcQWL/Nmy/nqsWTvTYg7oUmTGzglvSSV2P9h2z1PVeREPFf0GWNzruYleAt1XCMQZSg1xxQ==", + "peerDependencies": { + "fp-ts": "^2.0.0" + } }, "node_modules/ipaddr.js": { "version": "1.9.0", @@ -2825,6 +3050,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-interactive": { @@ -2858,7 +3086,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, "engines": { "node": ">=8" } @@ -2973,8 +3200,10 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-5.0.0.tgz", "integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==", "dependencies": { - "graceful-fs": "^4.1.6", "universalify": "^0.1.2" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, "node_modules/keyv": { @@ -2987,21 +3216,13 @@ } }, "node_modules/knub": { - "version": "30.0.0-beta.37", - "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.37.tgz", - "integrity": "sha512-eXgdhmD1bvGlqLG80mfLGwvkjgGZnTOhUXkO5dAjGujfiE02FbamKnVovw92ruUaB4HS35ydwJPTG3vvUuh/Sg==", - "dependencies": { - "knub-command-manager": "^9.1.0", - "ts-essentials": "^6.0.7" - }, - "peerDependencies": { - "eris": "^0.15.1" - } + "resolved": "../../Knub", + "link": true }, "node_modules/knub-command-manager": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/knub-command-manager/-/knub-command-manager-8.1.2.tgz", - "integrity": "sha512-/ooWzol2uxKRSi8INDaexXeRfLRJyboQUbwFFljkE3aYHihnf4ZNuKNgQImq2aUhSdLl9Jy/A1fCSjuooNsbtw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/knub-command-manager/-/knub-command-manager-9.1.0.tgz", + "integrity": "sha512-pEtpWElbBoTRSL8kWSPRrTIuTIdvYGkP/wzOn77cieumC02adfwEt1Cc09HFvVT4ib35nf1y31oul36csaG7Vg==", "dependencies": { "escape-string-regexp": "^2.0.0" } @@ -3014,22 +3235,6 @@ "node": ">=8" } }, - "node_modules/knub/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/knub/node_modules/knub-command-manager": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/knub-command-manager/-/knub-command-manager-9.1.0.tgz", - "integrity": "sha512-pEtpWElbBoTRSL8kWSPRrTIuTIdvYGkP/wzOn77cieumC02adfwEt1Cc09HFvVT4ib35nf1y31oul36csaG7Vg==", - "dependencies": { - "escape-string-regexp": "^2.0.0" - } - }, "node_modules/last-commit-log": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/last-commit-log/-/last-commit-log-2.1.0.tgz", @@ -3088,9 +3293,9 @@ } }, "node_modules/lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.chunk": { "version": "4.2.0", @@ -3163,6 +3368,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/make-dir/node_modules/semver": { @@ -3211,6 +3419,9 @@ "dev": true, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/md5-hex": { @@ -3350,58 +3561,14 @@ } }, "node_modules/minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, - "node_modules/minipass": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", - "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/minizlib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", - "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dependencies": { - "minimist": "0.0.8" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mkdirp/node_modules/minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, "node_modules/moment": { "version": "2.24.0", @@ -3463,9 +3630,9 @@ "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" }, "node_modules/napi-build-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.1.tgz", - "integrity": "sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" }, "node_modules/negotiator": { "version": "0.6.2", @@ -3481,34 +3648,42 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "node_modules/node-abi": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.13.0.tgz", - "integrity": "sha512-9HrZGFVTR5SOu3PZAnAY2hLO36aW1wmA+FDsVkr85BTST32TLCA1H/AEcatVRAsWLyXS3bqUDYCAjq5/QGuSTA==", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.0.tgz", + "integrity": "sha512-g6bZh3YCKQRdwuO/tSZZYJAw622SjsRfJ2X0Iy4sSOHZ34/sPPdVBn8fev2tj7njzLwuqPw9uMtGsGkO5kIQvg==", "dependencies": { "semver": "^5.4.1" } }, + "node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" + }, "node_modules/node-cleanup": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", "integrity": "sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=", "dev": true }, + "node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/node-gyp-build": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.7.0.tgz", - "integrity": "sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", + "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, - "node_modules/noop-logger": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", - "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" - }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -3531,9 +3706,9 @@ } }, "node_modules/normalize-url": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", "dev": true, "engines": { "node": ">=8" @@ -3602,12 +3777,6 @@ "node": ">=6" } }, - "node_modules/opusscript": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/opusscript/-/opusscript-0.0.8.tgz", - "integrity": "sha512-VSTi1aWFuCkRCVq+tx/BQ5q9fMnQ9pVZ3JU4UHKqTkf0ED3fKEPdr+gKAAl3IA2hj9rrP6iyq3hlcJq3HELtNQ==", - "optional": true - }, "node_modules/ora": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/ora/-/ora-4.0.4.tgz", @@ -3625,6 +3794,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ora/node_modules/ansi-regex": { @@ -3647,6 +3819,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/ora/node_modules/chalk": { @@ -3721,6 +3896,61 @@ "node": ">=0.10.0" } }, + "node_modules/ow": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/ow/-/ow-0.27.0.tgz", + "integrity": "sha512-SGnrGUbhn4VaUGdU0EJLMwZWSupPmF46hnTRII7aCLCrqixTAC5eKo8kI4/XXf1eaaI8YEVT+3FeGNJI9himAQ==", + "dependencies": { + "@sindresorhus/is": "^4.0.1", + "callsites": "^3.1.0", + "dot-prop": "^6.0.1", + "lodash.isequal": "^4.5.0", + "type-fest": "^1.2.1", + "vali-date": "^1.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ow/node_modules/@sindresorhus/is": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", + "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/ow/node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ow/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", @@ -3771,6 +4001,9 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-try": { @@ -3985,6 +4218,9 @@ "dev": true, "engines": { "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/pify": { @@ -4088,28 +4324,29 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/prebuild-install": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz", - "integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", + "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", "dependencies": { "detect-libc": "^1.0.3", "expand-template": "^2.0.3", "github-from-package": "0.0.0", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", "napi-build-utils": "^1.0.1", - "node-abi": "^2.7.0", - "noop-logger": "^0.1.1", + "node-abi": "^2.21.0", "npmlog": "^4.0.1", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^3.0.3", "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0", - "which-pm-runs": "^1.0.0" + "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" @@ -4137,6 +4374,9 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/process-nextick-args": { @@ -4379,6 +4619,9 @@ "dev": true, "dependencies": { "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/resolve-cwd": { @@ -4583,32 +4826,57 @@ } }, "node_modules/sharp": { - "version": "0.23.4", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.23.4.tgz", - "integrity": "sha512-fJMagt6cT0UDy9XCsgyLi0eiwWWhQRxbwGmqQT6sY8Av4s0SVsT/deg8fobBQCTDU5iXRgz0rAeXoE2LBZ8g+Q==", + "version": "0.28.3", + "resolved": "git+ssh://git@github.com/almeidx/sharp.git#68b4f387ae2ee1ee2dd8f289f5ec5fcf722fd3d3", + "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "color": "^3.1.2", + "color": "^3.1.3", "detect-libc": "^1.0.3", - "nan": "^2.14.0", - "npmlog": "^4.1.2", - "prebuild-install": "^5.3.3", - "semver": "^6.3.0", + "node-addon-api": "^3.2.0", + "prebuild-install": "^6.1.2", + "semver": "^7.3.5", "simple-get": "^3.1.0", - "tar": "^5.0.5", + "tar-fs": "^2.1.1", "tunnel-agent": "^0.6.0" }, "engines": { - "node": ">=8.5.0" + "node": ">=10" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/sharp/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, "node_modules/sharp/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": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, + "node_modules/sharp/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -4634,9 +4902,23 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "node_modules/simple-concat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", - "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/simple-get": { "version": "3.1.0", @@ -4660,11 +4942,14 @@ } }, "node_modules/simple-get/node_modules/mimic-response": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.0.0.tgz", - "integrity": "sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/simple-swizzle": { @@ -4714,6 +4999,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/slice-ansi/node_modules/color-convert": { @@ -4994,49 +5282,36 @@ "node": ">=4" } }, - "node_modules/tar": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/tar/-/tar-5.0.5.tgz", - "integrity": "sha512-MNIgJddrV2TkuwChwcSNds/5E9VijOiw7kAc1y5hTNJoLDSuIyid2QtLYiCYNnICebpuvjhPQZsXwUL0O3l7OQ==", - "dependencies": { - "chownr": "^1.1.3", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.0", - "mkdirp": "^0.5.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/tar-fs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz", - "integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "dependencies": { "chownr": "^1.1.1", - "mkdirp": "^0.5.1", + "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.0.0" + "tar-stream": "^2.1.4" } }, "node_modules/tar-stream": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.0.tgz", - "integrity": "sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dependencies": { - "bl": "^3.0.0", + "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" } }, "node_modules/tar-stream/node_modules/readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -5046,11 +5321,6 @@ "node": ">= 6" } }, - "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", @@ -5067,6 +5337,9 @@ "dev": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/thenify": { @@ -5160,10 +5433,10 @@ "node": ">=0.10.0" } }, - "node_modules/ts-essentials": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-6.0.7.tgz", - "integrity": "sha512-2E4HIIj4tQJlIHuATRHayv0EfMGK3ris/GRk1E3CFnsZzeNV+hUmelbaTZHLtXaZppM5oLhHRtO04gINC4Jusw==" + "node_modules/ts-mixer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.0.tgz", + "integrity": "sha512-nXIb1fvdY5CBSrDIblLn73NW0qRDk5yJ0Sk1qPBF560OdJfQp9jhl+0tzcY09OZ9U+6GpeoI9RjwoIKFIoB9MQ==" }, "node_modules/tsc-watch": { "version": "4.0.0", @@ -5182,6 +5455,9 @@ }, "engines": { "node": ">=6.4.0" + }, + "peerDependencies": { + "typescript": "*" } }, "node_modules/tsc-watch/node_modules/ansi-regex": { @@ -5243,12 +5519,6 @@ "node": "*" } }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "optional": true - }, "node_modules/twemoji": { "version": "12.1.4", "resolved": "https://registry.npmjs.org/twemoji/-/twemoji-12.1.4.tgz", @@ -5319,6 +5589,9 @@ }, "bin": { "typeorm": "cli.js" + }, + "funding": { + "url": "https://opencollective.com/typeorm" } }, "node_modules/typeorm/node_modules/ansi-styles": { @@ -5330,6 +5603,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/typeorm/node_modules/chalk": { @@ -5342,6 +5618,9 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/typeorm/node_modules/color-convert": { @@ -5369,6 +5648,11 @@ }, "engines": { "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/typeorm/node_modules/dotenv": { @@ -5393,6 +5677,9 @@ }, "engines": { "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/typeorm/node_modules/has-flag": { @@ -5443,9 +5730,9 @@ } }, "node_modules/typescript": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", - "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz", + "integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -5510,6 +5797,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" } }, "node_modules/update-notifier/node_modules/ansi-styles": { @@ -5523,6 +5813,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/update-notifier/node_modules/chalk": { @@ -5589,6 +5882,15 @@ "node": ">=4" } }, + "node_modules/utf-8-validate": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.5.tgz", + "integrity": "sha512-+pnxRYsS/axEpkrrEpzYfNZGXp0IjC/9RIxwM5gntY4Koi8SHmUGSfxfWqxZdRxrtaoVstuOzUp/rbs3JSPELQ==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.2.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -5603,13 +5905,22 @@ } }, "node_modules/uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", "bin": { "uuid": "bin/uuid" } }, + "node_modules/vali-date": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", + "integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -5663,11 +5974,6 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "node_modules/which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" - }, "node_modules/wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", @@ -5774,6 +6080,9 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/wrap-ansi/node_modules/ansi-regex": { @@ -5793,6 +6102,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/wrap-ansi/node_modules/color-convert": { @@ -5861,9 +6173,9 @@ } }, "node_modules/ws": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", - "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.1.tgz", + "integrity": "sha512-2c6faOUH/nhoQN6abwMloF7Iyl0ZS2E9HGtsiLrWn0zOOMWlhtDmdf/uihDt6jnuCxgtwGBNy6Onsoy2s2O2Ow==", "engines": { "node": ">=8.3.0" }, @@ -5918,12 +6230,6 @@ "@babel/runtime-corejs3": "^7.8.3" } }, - "node_modules/y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, "node_modules/yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", @@ -6069,7 +6375,10 @@ } }, "node_modules/yawn-yaml": { + "version": "1.5.0", "resolved": "git+ssh://git@github.com/dragory/yawn-yaml.git#77ab3870ca53c4693002c4a41336e7476e7934ed", + "integrity": "sha512-22gZa51NZQgBnoQzTqoNAXqNn8l7vonJhU+MEiqDXz6sA0GszMk0Un2xyN1p+8uhdq6ndFKz5qXeDC0eAyaMtQ==", + "license": "MIT", "dependencies": { "js-yaml": "^3.4.2", "lodash": "^4.17.11", @@ -6080,6 +6389,7 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/zlib-sync/-/zlib-sync-0.1.7.tgz", "integrity": "sha512-UmciU6ZrIwtwPC8noMzq+kGMdiWwNRZ3wC0SbED4Ew5Ikqx14MqDPRs/Pbk+3rZPh5SzsOgUBs1WRE0iieddpg==", + "hasInstallScript": true, "dependencies": { "nan": "^2.14.0" } @@ -6139,6 +6449,45 @@ } } }, + "@discordjs/builders": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.5.0.tgz", + "integrity": "sha512-HP5y4Rqw68o61Qv4qM5tVmDbWi4mdTFftqIOGRo33SNPpLJ1Ga3KEIR2ibKofkmsoQhEpLmopD1AZDs3cKpHuw==", + "requires": { + "@sindresorhus/is": "^4.0.1", + "discord-api-types": "^0.22.0", + "ow": "^0.27.0", + "ts-mixer": "^6.0.0", + "tslib": "^2.3.0" + }, + "dependencies": { + "@sindresorhus/is": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", + "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==" + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "@discordjs/collection": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.2.1.tgz", + "integrity": "sha512-vhxqzzM8gkomw0TYRF3tgx7SwElzUlXT/Aa41O7mOcyN6wIJfj5JmDWaO5XGKsGSsNx7F3i5oIlrucCCWV1Nog==" + }, + "@discordjs/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -6165,6 +6514,11 @@ "fastq": "^1.6.0" } }, + "@sapphire/async-queue": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.1.4.tgz", + "integrity": "sha512-fFrlF/uWpGOX5djw5Mu2Hnnrunao75WGey0sP0J3jnhmrJ5TAPzHYOmytD5iN/+pMxS+f+u/gezqHa9tPhRHEA==" + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -6388,6 +6742,7 @@ "version": "0.23.1", "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.23.1.tgz", "integrity": "sha512-iBRM9RjRF9pkIkukk6imlxfaKMRuiRND8L0yYKl5PJu5uLvxuNzp5f0x8aoTG5VX85M8O//BwbttzFVZL1j/FQ==", + "dev": true, "requires": { "@types/node": "*" } @@ -6401,7 +6756,16 @@ "@types/twemoji": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/@types/twemoji/-/twemoji-12.1.0.tgz", - "integrity": "sha512-dTHU1ZE83qUlF3oFWrdxKBmOimM+/3o9hzDBszcKjajmNu5G/DjWgQrRNkq+zxeR+zDN030ciAt5qTH+WXBD8A==" + "integrity": "sha512-dTHU1ZE83qUlF3oFWrdxKBmOimM+/3o9hzDBszcKjajmNu5G/DjWgQrRNkq+zxeR+zDN030ciAt5qTH+WXBD8A==", + "dev": true + }, + "@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "requires": { + "@types/node": "*" + } }, "accepts": { "version": "1.3.7", @@ -6535,6 +6899,11 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, "ava": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/ava/-/ava-3.10.0.tgz", @@ -6787,6 +7156,14 @@ "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" + }, + "dependencies": { + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + } } }, "yargs-parser": { @@ -6844,17 +7221,24 @@ } }, "bl": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-3.0.0.tgz", - "integrity": "sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "requires": { - "readable-stream": "^3.0.1" + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" }, "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -7026,11 +7410,11 @@ "dev": true }, "bufferutil": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.1.tgz", - "integrity": "sha512-xowrxvpxojqkagPcWRQVXZl0YXhRhAtBEIq3VoER1NH5Mw1n1o0ojdspp+GS2J//2gCVyrzQDApQ4unGF+QOoA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.3.tgz", + "integrity": "sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==", "requires": { - "node-gyp-build": "~3.7.0" + "node-gyp-build": "^4.2.0" } }, "bytes": { @@ -7073,8 +7457,7 @@ "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, "camelcase": { "version": "5.3.1", @@ -7110,9 +7493,9 @@ } }, "chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, "chunkd": { "version": "2.0.1", @@ -7337,12 +7720,12 @@ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", - "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" + "color-convert": "^1.9.3", + "color-string": "^1.6.0" } }, "color-convert": { @@ -7359,14 +7742,22 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz", + "integrity": "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==", "requires": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "common-path-prefix": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", @@ -7617,6 +8008,11 @@ } } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -7646,6 +8042,26 @@ "path-type": "^4.0.0" } }, + "discord-api-types": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.22.0.tgz", + "integrity": "sha512-l8yD/2zRbZItUQpy7ZxBJwaLX/Bs2TGaCthRppk8Sw24LOIWg12t9JEreezPoYD0SQcC2htNNo27kYEpYW/Srg==" + }, + "discord.js": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.1.0.tgz", + "integrity": "sha512-gxO4CXKdHpqA+WKG+f5RNnd3srTDj5uFJHgOathksDE90YNq/Qijkd2WlMgTTMS6AJoEnHxI7G9eDQHCuZ+xDA==", + "requires": { + "@discordjs/builders": "^0.5.0", + "@discordjs/collection": "^0.2.1", + "@discordjs/form-data": "^3.0.1", + "@sapphire/async-queue": "^1.1.4", + "@types/ws": "^7.4.7", + "discord-api-types": "^0.22.0", + "node-fetch": "^2.6.1", + "ws": "^7.5.1" + } + }, "dot-prop": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", @@ -7715,19 +8131,9 @@ "integrity": "sha1-IcoRLUirJLTh5//A5TOdMf38J0w=", "dev": true }, - "eris": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/eris/-/eris-0.15.1.tgz", - "integrity": "sha512-IQ3BPW6OjgFoqjdh+irPOa1jFlkotk+WNu2GQQ7QAQfbzQEPZgn+F+hpOxfMUXPHOZMX4sPKLkVDkMHAssBYhw==", - "requires": { - "opusscript": "^0.0.8", - "tweetnacl": "^1.0.1", - "ws": "^7.2.1" - } - }, "erlpack": { - "version": "git+ssh://git@github.com/abalabahaha/erlpack.git#5d0064f9e106841e1eead711a6451f99b0d289fd", - "from": "erlpack@github:abalabahaha/erlpack", + "version": "git+ssh://git@github.com/almeidx/erlpack.git#f0c535f73817fd914806d6ca26a7730c14e0fb7c", + "from": "erlpack@github:almeidx/erlpack#f0c535f73817fd914806d6ca26a7730c14e0fb7c", "requires": { "bindings": "^1.5.0", "nan": "^2.14.0" @@ -7961,14 +8367,6 @@ } } }, - "fs-minipass": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.0.0.tgz", - "integrity": "sha512-40Qz+LFXmd9tzYVnnBmZvFfvAADfUA14TXPK1s7IfElJTIZ97rA8w4Kin7Wt5JBrC3ShnnFJO/5vPjPEeJIq9A==", - "requires": { - "minipass": "^3.0.0" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -8063,9 +8461,9 @@ } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -8156,9 +8554,9 @@ "integrity": "sha512-8mlRcn5vk/r4+QcqerapwBYTe+iPL5ih6xrNylxrnBdHQiijDETfXX7VIxC3UiCRiINBJfANBAsPzAvRQj8RpQ==" }, "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "http-cache-semantics": { @@ -8252,14 +8650,15 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "io-ts": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.0.1.tgz", - "integrity": "sha512-RezD+WcCfW4VkMkEcQWL/Nmy/nqsWTvTYg7oUmTGzglvSSV2P9h2z1PVeREPFf0GWNzruYleAt1XCMQZSg1xxQ==" + "integrity": "sha512-RezD+WcCfW4VkMkEcQWL/Nmy/nqsWTvTYg7oUmTGzglvSSV2P9h2z1PVeREPFf0GWNzruYleAt1XCMQZSg1xxQ==", + "requires": {} }, "ipaddr.js": { "version": "1.9.0", @@ -8353,8 +8752,7 @@ "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" }, "is-path-cwd": { "version": "2.2.0", @@ -8462,33 +8860,32 @@ } }, "knub": { - "version": "30.0.0-beta.37", - "resolved": "https://registry.npmjs.org/knub/-/knub-30.0.0-beta.37.tgz", - "integrity": "sha512-eXgdhmD1bvGlqLG80mfLGwvkjgGZnTOhUXkO5dAjGujfiE02FbamKnVovw92ruUaB4HS35ydwJPTG3vvUuh/Sg==", + "version": "file:../../Knub", "requires": { + "@types/chai": "^4.2.18", + "@types/mocha": "^7.0.2", + "@types/node": "^14.14.45", + "@typescript-eslint/eslint-plugin": "^4.23.0", + "@typescript-eslint/parser": "^4.23.0", + "chai": "^4.3.4", + "discord-api-types": "^0.22.0", + "discord.js": "^13.1.0", + "eslint": "^7.2.0", + "husky": "^4.3.8", "knub-command-manager": "^9.1.0", - "ts-essentials": "^6.0.7" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" - }, - "knub-command-manager": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/knub-command-manager/-/knub-command-manager-9.1.0.tgz", - "integrity": "sha512-pEtpWElbBoTRSL8kWSPRrTIuTIdvYGkP/wzOn77cieumC02adfwEt1Cc09HFvVT4ib35nf1y31oul36csaG7Vg==", - "requires": { - "escape-string-regexp": "^2.0.0" - } - } + "lint-staged": "^10.5.4", + "mocha": "^8.4.0", + "prettier": "^2.3.0", + "shx": "^0.3.3", + "ts-essentials": "^6.0.7", + "ts-node": "^8.10.2", + "typescript": "^4.2.4" } }, "knub-command-manager": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/knub-command-manager/-/knub-command-manager-8.1.2.tgz", - "integrity": "sha512-/ooWzol2uxKRSi8INDaexXeRfLRJyboQUbwFFljkE3aYHihnf4ZNuKNgQImq2aUhSdLl9Jy/A1fCSjuooNsbtw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/knub-command-manager/-/knub-command-manager-9.1.0.tgz", + "integrity": "sha512-pEtpWElbBoTRSL8kWSPRrTIuTIdvYGkP/wzOn77cieumC02adfwEt1Cc09HFvVT4ib35nf1y31oul36csaG7Vg==", "requires": { "escape-string-regexp": "^2.0.0" }, @@ -8546,9 +8943,9 @@ } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.chunk": { "version": "4.2.0", @@ -8751,55 +9148,14 @@ } }, "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, - "minipass": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", - "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", - "requires": { - "yallist": "^4.0.0" - }, - "dependencies": { - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } - }, - "minizlib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", - "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "dependencies": { - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } - } + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, "moment": { "version": "2.24.0", @@ -8852,9 +9208,9 @@ "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" }, "napi-build-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.1.tgz", - "integrity": "sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" }, "negotiator": { "version": "0.6.2", @@ -8867,28 +9223,33 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "node-abi": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.13.0.tgz", - "integrity": "sha512-9HrZGFVTR5SOu3PZAnAY2hLO36aW1wmA+FDsVkr85BTST32TLCA1H/AEcatVRAsWLyXS3bqUDYCAjq5/QGuSTA==", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.0.tgz", + "integrity": "sha512-g6bZh3YCKQRdwuO/tSZZYJAw622SjsRfJ2X0Iy4sSOHZ34/sPPdVBn8fev2tj7njzLwuqPw9uMtGsGkO5kIQvg==", "requires": { "semver": "^5.4.1" } }, + "node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" + }, "node-cleanup": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", "integrity": "sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=", "dev": true }, - "node-gyp-build": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.7.0.tgz", - "integrity": "sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==" + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, - "noop-logger": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", - "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" + "node-gyp-build": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", + "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==" }, "normalize-package-data": { "version": "2.5.0", @@ -8909,9 +9270,9 @@ "dev": true }, "normalize-url": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", "dev": true }, "npmlog": { @@ -8965,12 +9326,6 @@ "mimic-fn": "^2.1.0" } }, - "opusscript": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/opusscript/-/opusscript-0.0.8.tgz", - "integrity": "sha512-VSTi1aWFuCkRCVq+tx/BQ5q9fMnQ9pVZ3JU4UHKqTkf0ED3fKEPdr+gKAAl3IA2hj9rrP6iyq3hlcJq3HELtNQ==", - "optional": true - }, "ora": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/ora/-/ora-4.0.4.tgz", @@ -9059,6 +9414,39 @@ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, + "ow": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/ow/-/ow-0.27.0.tgz", + "integrity": "sha512-SGnrGUbhn4VaUGdU0EJLMwZWSupPmF46hnTRII7aCLCrqixTAC5eKo8kI4/XXf1eaaI8YEVT+3FeGNJI9himAQ==", + "requires": { + "@sindresorhus/is": "^4.0.1", + "callsites": "^3.1.0", + "dot-prop": "^6.0.1", + "lodash.isequal": "^4.5.0", + "type-fest": "^1.2.1", + "vali-date": "^1.0.0" + }, + "dependencies": { + "@sindresorhus/is": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", + "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==" + }, + "dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "requires": { + "is-obj": "^2.0.0" + } + }, + "type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==" + } + } + }, "p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", @@ -9347,25 +9735,23 @@ } }, "prebuild-install": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz", - "integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", + "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", "requires": { "detect-libc": "^1.0.3", "expand-template": "^2.0.3", "github-from-package": "0.0.0", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", "napi-build-utils": "^1.0.1", - "node-abi": "^2.7.0", - "noop-logger": "^0.1.1", + "node-abi": "^2.21.0", "npmlog": "^4.0.1", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^3.0.3", "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0", - "which-pm-runs": "^1.0.0" + "tunnel-agent": "^0.6.0" } }, "prepend-http": { @@ -9745,25 +10131,39 @@ } }, "sharp": { - "version": "0.23.4", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.23.4.tgz", - "integrity": "sha512-fJMagt6cT0UDy9XCsgyLi0eiwWWhQRxbwGmqQT6sY8Av4s0SVsT/deg8fobBQCTDU5iXRgz0rAeXoE2LBZ8g+Q==", + "version": "git+ssh://git@github.com/almeidx/sharp.git#68b4f387ae2ee1ee2dd8f289f5ec5fcf722fd3d3", + "from": "sharp@github:almeidx/sharp#68b4f387ae2ee1ee2dd8f289f5ec5fcf722fd3d3", "requires": { - "color": "^3.1.2", + "color": "^3.1.3", "detect-libc": "^1.0.3", - "nan": "^2.14.0", - "npmlog": "^4.1.2", - "prebuild-install": "^5.3.3", - "semver": "^6.3.0", + "node-addon-api": "^3.2.0", + "prebuild-install": "^6.1.2", + "semver": "^7.3.5", "simple-get": "^3.1.0", - "tar": "^5.0.5", + "tar-fs": "^2.1.1", "tunnel-agent": "^0.6.0" }, "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, @@ -9786,9 +10186,9 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "simple-concat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", - "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" }, "simple-get": { "version": "3.1.0", @@ -9809,9 +10209,9 @@ } }, "mimic-response": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.0.0.tgz", - "integrity": "sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" } } }, @@ -10086,43 +10486,23 @@ "has-flag": "^3.0.0" } }, - "tar": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/tar/-/tar-5.0.5.tgz", - "integrity": "sha512-MNIgJddrV2TkuwChwcSNds/5E9VijOiw7kAc1y5hTNJoLDSuIyid2QtLYiCYNnICebpuvjhPQZsXwUL0O3l7OQ==", - "requires": { - "chownr": "^1.1.3", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.0", - "mkdirp": "^0.5.0", - "yallist": "^4.0.0" - }, - "dependencies": { - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } - }, "tar-fs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz", - "integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "requires": { "chownr": "^1.1.1", - "mkdirp": "^0.5.1", + "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.0.0" + "tar-stream": "^2.1.4" } }, "tar-stream": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.0.tgz", - "integrity": "sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "requires": { - "bl": "^3.0.0", + "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", @@ -10130,9 +10510,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -10220,10 +10600,10 @@ "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=", "dev": true }, - "ts-essentials": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-6.0.7.tgz", - "integrity": "sha512-2E4HIIj4tQJlIHuATRHayv0EfMGK3ris/GRk1E3CFnsZzeNV+hUmelbaTZHLtXaZppM5oLhHRtO04gINC4Jusw==" + "ts-mixer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.0.tgz", + "integrity": "sha512-nXIb1fvdY5CBSrDIblLn73NW0qRDk5yJ0Sk1qPBF560OdJfQp9jhl+0tzcY09OZ9U+6GpeoI9RjwoIKFIoB9MQ==" }, "tsc-watch": { "version": "4.0.0", @@ -10290,12 +10670,6 @@ "safe-buffer": "^5.0.1" } }, - "tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "optional": true - }, "twemoji": { "version": "12.1.4", "resolved": "https://registry.npmjs.org/twemoji/-/twemoji-12.1.4.tgz", @@ -10450,9 +10824,9 @@ } }, "typescript": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", - "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz", + "integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==", "dev": true }, "uid2": { @@ -10561,6 +10935,14 @@ "prepend-http": "^2.0.0" } }, + "utf-8-validate": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.5.tgz", + "integrity": "sha512-+pnxRYsS/axEpkrrEpzYfNZGXp0IjC/9RIxwM5gntY4Koi8SHmUGSfxfWqxZdRxrtaoVstuOzUp/rbs3JSPELQ==", + "requires": { + "node-gyp-build": "^4.2.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -10572,9 +10954,14 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "vali-date": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", + "integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=" }, "validate-npm-package-license": { "version": "3.0.4", @@ -10620,11 +11007,6 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" - }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", @@ -10779,9 +11161,9 @@ } }, "ws": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", - "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.1.tgz", + "integrity": "sha512-2c6faOUH/nhoQN6abwMloF7Iyl0ZS2E9HGtsiLrWn0zOOMWlhtDmdf/uihDt6jnuCxgtwGBNy6Onsoy2s2O2Ow==", "requires": {} }, "xdg-basedir": { @@ -10813,12 +11195,6 @@ "@babel/runtime-corejs3": "^7.8.3" } }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", @@ -10933,6 +11309,7 @@ }, "yawn-yaml": { "version": "git+ssh://git@github.com/dragory/yawn-yaml.git#77ab3870ca53c4693002c4a41336e7476e7934ed", + "integrity": "sha512-22gZa51NZQgBnoQzTqoNAXqNn8l7vonJhU+MEiqDXz6sA0GszMk0Un2xyN1p+8uhdq6ndFKz5qXeDC0eAyaMtQ==", "from": "yawn-yaml@github:dragory/yawn-yaml#string-number-fix-build", "requires": { "js-yaml": "^3.4.2", diff --git a/backend/package.json b/backend/package.json index 45332a42..3f9589e4 100644 --- a/backend/package.json +++ b/backend/package.json @@ -23,24 +23,23 @@ "test-watch": "tsc-watch --onSuccess \"npx ava\"" }, "dependencies": { - "@types/sharp": "^0.23.1", - "@types/twemoji": "^12.1.0", - "bufferutil": "^4.0.1", + "bufferutil": "^4.0.3", "cors": "^2.8.5", "cross-env": "^5.2.0", "deep-diff": "^1.0.2", + "discord-api-types": "^0.22.0", + "discord.js": "^13.1.0", "dotenv": "^4.0.0", "emoji-regex": "^8.0.0", - "eris": "^0.15.1", - "erlpack": "github:abalabahaha/erlpack", + "erlpack": "github:almeidx/erlpack#f0c535f73817fd914806d6ca26a7730c14e0fb7c", "escape-string-regexp": "^1.0.5", "express": "^4.17.0", "fp-ts": "^2.0.1", "humanize-duration": "^3.15.0", "io-ts": "^2.0.0", "js-yaml": "^3.13.1", - "knub": "^30.0.0-beta.37", - "knub-command-manager": "^8.1.2", + "knub": "file:../../Knub", + "knub-command-manager": "^9.1.0", "last-commit-log": "^2.1.0", "lodash.chunk": "^4.2.0", "lodash.clonedeep": "^4.5.0", @@ -59,13 +58,14 @@ "regexp-worker": "^1.1.0", "safe-regex": "^2.0.2", "seedrandom": "^3.0.1", - "sharp": "^0.23.4", + "sharp": "github:almeidx/sharp#68b4f387ae2ee1ee2dd8f289f5ec5fcf722fd3d3", "strip-combining-marks": "^1.0.0", "tlds": "^1.203.1", "tmp": "0.0.33", "tsconfig-paths": "^3.9.0", "twemoji": "^12.1.4", "typeorm": "^0.2.31", + "utf-8-validate": "^5.0.5", "uuid": "^3.3.2", "yawn-yaml": "github:dragory/yawn-yaml#string-number-fix-build", "zlib-sync": "^0.1.7" @@ -82,12 +82,14 @@ "@types/passport-oauth2": "^1.4.8", "@types/passport-strategy": "^0.2.35", "@types/safe-regex": "^1.1.2", + "@types/sharp": "^0.23.1", "@types/tmp": "0.0.33", + "@types/twemoji": "^12.1.0", "ava": "^3.10.0", "rimraf": "^2.6.2", "source-map-support": "^0.5.16", "tsc-watch": "^4.0.0", - "typescript": "^4.1.3" + "typescript": "^4.3.4" }, "ava": { "files": [ diff --git a/backend/src/ErisError.ts b/backend/src/DiscordJSError.ts similarity index 65% rename from backend/src/ErisError.ts rename to backend/src/DiscordJSError.ts index 732fa53b..26a3b8ed 100644 --- a/backend/src/ErisError.ts +++ b/backend/src/DiscordJSError.ts @@ -1,6 +1,6 @@ import util from "util"; -export class ErisError extends Error { +export class DiscordJSError extends Error { code: number | string | undefined; shardId: number; @@ -11,6 +11,6 @@ export class ErisError extends Error { } [util.inspect.custom]() { - return `[ERIS] [ERROR CODE ${this.code || "?"}] [SHARD ${this.shardId}] ${this.message}`; + return `[DISCORDJS] [ERROR CODE ${this.code ?? "?"}] [SHARD ${this.shardId}] ${this.message}`; } } diff --git a/backend/src/RecoverablePluginError.ts b/backend/src/RecoverablePluginError.ts index 3cb1c7ac..e34a2ab2 100644 --- a/backend/src/RecoverablePluginError.ts +++ b/backend/src/RecoverablePluginError.ts @@ -1,4 +1,4 @@ -import { Guild } from "eris"; +import { Guild } from "discord.js"; export enum ERRORS { NO_MUTE_ROLE_IN_CONFIG = 1, diff --git a/backend/src/RegExpRunner.ts b/backend/src/RegExpRunner.ts index d66df414..96d39be3 100644 --- a/backend/src/RegExpRunner.ts +++ b/backend/src/RegExpRunner.ts @@ -1,7 +1,7 @@ -import { RegExpWorker, TimeoutError } from "regexp-worker"; -import { CooldownManager } from "knub"; -import { MINUTES, SECONDS } from "./utils"; import { EventEmitter } from "events"; +import { CooldownManager } from "knub"; +import { RegExpWorker, TimeoutError } from "regexp-worker"; +import { MINUTES, SECONDS } from "./utils"; import Timeout = NodeJS.Timeout; const isTimeoutError = (a): a is TimeoutError => { diff --git a/backend/src/api/archives.ts b/backend/src/api/archives.ts index b1330a98..3c1ae702 100644 --- a/backend/src/api/archives.ts +++ b/backend/src/api/archives.ts @@ -1,7 +1,7 @@ import express, { Request, Response } from "express"; +import moment from "moment-timezone"; import { GuildArchives } from "../data/GuildArchives"; import { notFound } from "./responses"; -import moment from "moment-timezone"; export function initArchives(app: express.Express) { const archives = new GuildArchives(null); diff --git a/backend/src/api/auth.ts b/backend/src/api/auth.ts index 2a5d3445..c891ac63 100644 --- a/backend/src/api/auth.ts +++ b/backend/src/api/auth.ts @@ -1,13 +1,13 @@ import express, { Request, Response } from "express"; -import passport from "passport"; -import OAuth2Strategy from "passport-oauth2"; -import { Strategy as CustomStrategy } from "passport-custom"; -import { ApiLogins } from "../data/ApiLogins"; -import pick from "lodash.pick"; import https from "https"; +import pick from "lodash.pick"; +import passport from "passport"; +import { Strategy as CustomStrategy } from "passport-custom"; +import OAuth2Strategy from "passport-oauth2"; +import { ApiLogins } from "../data/ApiLogins"; +import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments"; import { ApiUserInfo } from "../data/ApiUserInfo"; import { ApiUserInfoData } from "../data/entities/ApiUserInfo"; -import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments"; import { ok } from "./responses"; interface IPassportApiUser { diff --git a/backend/src/api/docs.ts b/backend/src/api/docs.ts index 22a70fa0..3009a4e1 100644 --- a/backend/src/api/docs.ts +++ b/backend/src/api/docs.ts @@ -1,7 +1,7 @@ import express from "express"; import { guildPlugins } from "../plugins/availablePlugins"; -import { notFound } from "./responses"; import { indentLines } from "../utils"; +import { notFound } from "./responses"; function formatConfigSchema(schema) { if (schema._tag === "InterfaceType" || schema._tag === "PartialType") { diff --git a/backend/src/api/guilds.ts b/backend/src/api/guilds.ts index 213dcb44..8969b29c 100644 --- a/backend/src/api/guilds.ts +++ b/backend/src/api/guilds.ts @@ -1,13 +1,13 @@ -import express, { Request, Response } from "express"; -import { AllowedGuilds } from "../data/AllowedGuilds"; -import { clientError, ok, serverError, unauthorized } from "./responses"; -import { Configs } from "../data/Configs"; -import { validateGuildConfig } from "../configValidator"; -import yaml, { YAMLException } from "js-yaml"; -import { apiTokenAuthHandlers } from "./auth"; import { ApiPermissions } from "@shared/apiPermissions"; -import { hasGuildPermission, requireGuildPermission } from "./permissions"; +import express, { Request, Response } from "express"; +import yaml, { YAMLException } from "js-yaml"; +import { validateGuildConfig } from "../configValidator"; +import { AllowedGuilds } from "../data/AllowedGuilds"; import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments"; +import { Configs } from "../data/Configs"; +import { apiTokenAuthHandlers } from "./auth"; +import { hasGuildPermission, requireGuildPermission } from "./permissions"; +import { clientError, ok, serverError, unauthorized } from "./responses"; const apiPermissionAssignments = new ApiPermissionAssignments(); diff --git a/backend/src/api/index.ts b/backend/src/api/index.ts index 7579c34e..cd2ae878 100644 --- a/backend/src/api/index.ts +++ b/backend/src/api/index.ts @@ -1,8 +1,6 @@ -import "./loadEnv"; - import { connect } from "../data/db"; -import path from "path"; import { setIsAPI } from "../globals"; +import "./loadEnv"; if (!process.env.KEY) { // tslint:disable-next-line:no-console diff --git a/backend/src/api/permissions.ts b/backend/src/api/permissions.ts index 7433734a..9552aaf4 100644 --- a/backend/src/api/permissions.ts +++ b/backend/src/api/permissions.ts @@ -1,7 +1,7 @@ import { ApiPermissions, hasPermission, permissionArrToSet } from "@shared/apiPermissions"; -import { isStaff } from "../staff"; -import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments"; import { Request, Response } from "express"; +import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments"; +import { isStaff } from "../staff"; import { unauthorized } from "./responses"; const apiPermissionAssignments = new ApiPermissionAssignments(); diff --git a/backend/src/api/staff.ts b/backend/src/api/staff.ts index 82109735..08f0dc09 100644 --- a/backend/src/api/staff.ts +++ b/backend/src/api/staff.ts @@ -1,6 +1,6 @@ import express, { Request, Response } from "express"; -import { apiTokenAuthHandlers } from "./auth"; import { isStaff } from "../staff"; +import { apiTokenAuthHandlers } from "./auth"; export function initStaff(app: express.Express) { const staffRouter = express.Router(); diff --git a/backend/src/api/start.ts b/backend/src/api/start.ts index 0932edd4..2e3e2c7d 100644 --- a/backend/src/api/start.ts +++ b/backend/src/api/start.ts @@ -1,11 +1,11 @@ -import { clientError, error, notFound } from "./responses"; -import express from "express"; import cors from "cors"; -import { initAuth } from "./auth"; -import { initGuildsAPI } from "./guilds"; -import { initArchives } from "./archives"; -import { initDocs } from "./docs"; +import express from "express"; import { TokenError } from "passport-oauth2"; +import { initArchives } from "./archives"; +import { initAuth } from "./auth"; +import { initDocs } from "./docs"; +import { initGuildsAPI } from "./guilds"; +import { clientError, error, notFound } from "./responses"; const app = express(); diff --git a/backend/src/commandTypes.ts b/backend/src/commandTypes.ts index cfebf1ac..e3f1ece3 100644 --- a/backend/src/commandTypes.ts +++ b/backend/src/commandTypes.ts @@ -1,9 +1,9 @@ +import { GuildChannel, GuildMember, Snowflake, Util, User } from "discord.js"; +import { baseCommandParameterTypeHelpers, baseTypeConverters, CommandContext, TypeConversionError } from "knub"; +import { createTypeHelper } from "knub-command-manager"; import { channelMentionRegex, convertDelayStringToMS, - disableCodeBlocks, - disableInlineCode, - isSnowflake, isValidSnowflake, resolveMember, resolveUser, @@ -11,13 +11,9 @@ import { roleMentionRegex, UnknownUser, } from "./utils"; -import { GuildChannel, Member, TextChannel, User } from "eris"; -import { baseTypeConverters, baseCommandParameterTypeHelpers, CommandContext, TypeConversionError } from "knub"; -import { createTypeHelper } from "knub-command-manager"; -import { getChannelIdFromMessageId } from "./data/getChannelIdFromMessageId"; +import { isValidTimezone } from "./utils/isValidTimezone"; import { MessageTarget, resolveMessageTarget } from "./utils/resolveMessageTarget"; import { inputPatternToRegExp } from "./validatorUtils"; -import { isValidTimezone } from "./utils/isValidTimezone"; export const commandTypes = { ...baseTypeConverters, @@ -34,7 +30,7 @@ export const commandTypes = { async resolvedUser(value, context: CommandContext) { const result = await resolveUser(context.pluginData.client, value); if (result == null || result instanceof UnknownUser) { - throw new TypeConversionError(`User \`${disableCodeBlocks(value)}\` was not found`); + throw new TypeConversionError(`User \`${Util.escapeCodeBlock(value)}\` was not found`); } return result; }, @@ -42,7 +38,7 @@ export const commandTypes = { async resolvedUserLoose(value, context: CommandContext) { const result = await resolveUser(context.pluginData.client, value); if (result == null) { - throw new TypeConversionError(`Invalid user: \`${disableCodeBlocks(value)}\``); + throw new TypeConversionError(`Invalid user: \`${Util.escapeCodeBlock(value)}\``); } return result; }, @@ -55,7 +51,7 @@ export const commandTypes = { const result = await resolveMember(context.pluginData.client, context.message.channel.guild, value); if (result == null) { throw new TypeConversionError( - `Member \`${disableCodeBlocks(value)}\` was not found or they have left the server`, + `Member \`${Util.escapeCodeBlock(value)}\` was not found or they have left the server`, ); } return result; @@ -66,7 +62,7 @@ export const commandTypes = { const result = await resolveMessageTarget(context.pluginData, value); if (!result) { - throw new TypeConversionError(`Unknown message \`${disableInlineCode(value)}\``); + throw new TypeConversionError(`Unknown message \`${Util.escapeInlineCode(value)}\``); } return result; @@ -74,32 +70,32 @@ export const commandTypes = { async anyId(value: string, context: CommandContext) { const userId = resolveUserId(context.pluginData.client, value); - if (userId) return userId; + if (userId) return userId as Snowflake; const channelIdMatch = value.match(channelMentionRegex); - if (channelIdMatch) return channelIdMatch[1]; + if (channelIdMatch) return channelIdMatch[1] as Snowflake; const roleIdMatch = value.match(roleMentionRegex); - if (roleIdMatch) return roleIdMatch[1]; + if (roleIdMatch) return roleIdMatch[1] as Snowflake; if (isValidSnowflake(value)) { - return value; + return value as Snowflake; } - throw new TypeConversionError(`Could not parse ID: \`${disableInlineCode(value)}\``); + throw new TypeConversionError(`Could not parse ID: \`${Util.escapeInlineCode(value)}\``); }, regex(value: string, context: CommandContext): RegExp { try { return inputPatternToRegExp(value); } catch (e) { - throw new TypeConversionError(`Could not parse RegExp: \`${disableInlineCode(e.message)}\``); + throw new TypeConversionError(`Could not parse RegExp: \`${Util.escapeInlineCode(e.message)}\``); } }, timezone(value: string) { if (!isValidTimezone(value)) { - throw new TypeConversionError(`Invalid timezone: ${disableInlineCode(value)}`); + throw new TypeConversionError(`Invalid timezone: ${Util.escapeInlineCode(value)}`); } return value; @@ -112,9 +108,9 @@ export const commandTypeHelpers = { delay: createTypeHelper(commandTypes.delay), resolvedUser: createTypeHelper>(commandTypes.resolvedUser), resolvedUserLoose: createTypeHelper>(commandTypes.resolvedUserLoose), - resolvedMember: createTypeHelper>(commandTypes.resolvedMember), + resolvedMember: createTypeHelper>(commandTypes.resolvedMember), messageTarget: createTypeHelper>(commandTypes.messageTarget), - anyId: createTypeHelper>(commandTypes.anyId), + anyId: createTypeHelper>(commandTypes.anyId), regex: createTypeHelper(commandTypes.regex), timezone: createTypeHelper(commandTypes.timezone), }; diff --git a/backend/src/configValidator.ts b/backend/src/configValidator.ts index 9ea4e804..0c45c611 100644 --- a/backend/src/configValidator.ts +++ b/backend/src/configValidator.ts @@ -1,10 +1,9 @@ -import * as t from "io-ts"; -import { guildPlugins } from "./plugins/availablePlugins"; -import { decodeAndValidateStrict, StrictValidationError } from "./validatorUtils"; -import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin"; -import { PartialZeppelinGuildConfigSchema, ZeppelinGuildConfig } from "./types"; import { configUtils, ConfigValidationError, PluginOptions } from "knub"; import moment from "moment-timezone"; +import { guildPlugins } from "./plugins/availablePlugins"; +import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin"; +import { PartialZeppelinGuildConfigSchema, ZeppelinGuildConfig } from "./types"; +import { decodeAndValidateStrict, StrictValidationError } from "./validatorUtils"; const pluginNameToPlugin = new Map(); for (const plugin of guildPlugins) { diff --git a/backend/src/data/AllowedGuilds.ts b/backend/src/data/AllowedGuilds.ts index 0efe9770..e3399fea 100644 --- a/backend/src/data/AllowedGuilds.ts +++ b/backend/src/data/AllowedGuilds.ts @@ -1,7 +1,7 @@ -import { AllowedGuild } from "./entities/AllowedGuild"; import { getRepository, Repository } from "typeorm"; -import { BaseRepository } from "./BaseRepository"; import { ApiPermissionTypes } from "./ApiPermissionAssignments"; +import { BaseRepository } from "./BaseRepository"; +import { AllowedGuild } from "./entities/AllowedGuild"; export class AllowedGuilds extends BaseRepository { private allowedGuilds: Repository; diff --git a/backend/src/data/ApiLogins.ts b/backend/src/data/ApiLogins.ts index 645fcd47..23bb7e49 100644 --- a/backend/src/data/ApiLogins.ts +++ b/backend/src/data/ApiLogins.ts @@ -1,11 +1,11 @@ -import { getRepository, Repository } from "typeorm"; -import { ApiLogin } from "./entities/ApiLogin"; -import { BaseRepository } from "./BaseRepository"; import crypto from "crypto"; import moment from "moment-timezone"; +import { getRepository, Repository } from "typeorm"; // tslint:disable-next-line:no-submodule-imports import uuidv4 from "uuid/v4"; import { DAYS, DBDateFormat } from "../utils"; +import { BaseRepository } from "./BaseRepository"; +import { ApiLogin } from "./entities/ApiLogin"; const LOGIN_EXPIRY_TIME = 1 * DAYS; diff --git a/backend/src/data/ApiPermissionAssignments.ts b/backend/src/data/ApiPermissionAssignments.ts index dbcc0e26..29686adc 100644 --- a/backend/src/data/ApiPermissionAssignments.ts +++ b/backend/src/data/ApiPermissionAssignments.ts @@ -1,7 +1,7 @@ -import { getRepository, Repository } from "typeorm"; -import { ApiPermissionAssignment } from "./entities/ApiPermissionAssignment"; -import { BaseRepository } from "./BaseRepository"; import { ApiPermissions } from "@shared/apiPermissions"; +import { getRepository, Repository } from "typeorm"; +import { BaseRepository } from "./BaseRepository"; +import { ApiPermissionAssignment } from "./entities/ApiPermissionAssignment"; export enum ApiPermissionTypes { User = "USER", diff --git a/backend/src/data/ApiUserInfo.ts b/backend/src/data/ApiUserInfo.ts index 09d9df75..73633d01 100644 --- a/backend/src/data/ApiUserInfo.ts +++ b/backend/src/data/ApiUserInfo.ts @@ -1,9 +1,9 @@ +import moment from "moment-timezone"; import { getRepository, Repository } from "typeorm"; -import { ApiUserInfo as ApiUserInfoEntity, ApiUserInfoData } from "./entities/ApiUserInfo"; +import { DBDateFormat } from "../utils"; import { BaseRepository } from "./BaseRepository"; import { connection } from "./db"; -import moment from "moment-timezone"; -import { DBDateFormat } from "../utils"; +import { ApiUserInfo as ApiUserInfoEntity, ApiUserInfoData } from "./entities/ApiUserInfo"; export class ApiUserInfo extends BaseRepository { private apiUserInfo: Repository; diff --git a/backend/src/data/Configs.ts b/backend/src/data/Configs.ts index 054ce255..3e0882f0 100644 --- a/backend/src/data/Configs.ts +++ b/backend/src/data/Configs.ts @@ -1,10 +1,10 @@ -import { Config } from "./entities/Config"; import { getRepository, Repository } from "typeorm"; -import { connection } from "./db"; -import { BaseRepository } from "./BaseRepository"; import { isAPI } from "../globals"; import { HOURS, SECONDS } from "../utils"; +import { BaseRepository } from "./BaseRepository"; import { cleanupConfigs } from "./cleanup/configs"; +import { connection } from "./db"; +import { Config } from "./entities/Config"; if (isAPI()) { const CLEANUP_INTERVAL = 1 * HOURS; diff --git a/backend/src/data/DefaultLogMessages.json b/backend/src/data/DefaultLogMessages.json index 0c6c010e..c666ff43 100644 --- a/backend/src/data/DefaultLogMessages.json +++ b/backend/src/data/DefaultLogMessages.json @@ -24,11 +24,15 @@ "CHANNEL_CREATE": "🖊 Channel {channelMention(channel)} was created", "CHANNEL_DELETE": "🗑 Channel {channelMention(channel)} was deleted", - "CHANNEL_EDIT": "✏ Channel {channelMention(channel)} was edited", + "CHANNEL_UPDATE": "✏ Channel {channelMention(newChannel)} was edited. Changes:\n{differenceString}", + + "THREAD_CREATE": "🖊 Thread {channelMention(thread)} was created in channel <#{thread.parentId}>", + "THREAD_DELETE": "🗑 Thread {channelMention(thread)} was deleted/archived from channel <#{thread.parentId}>", + "THREAD_UPDATE": "✏ Thread {channelMention(newThread)} was edited. Changes:\n{differenceString}", "ROLE_CREATE": "🖊 Role **{role.name}** (`{role.id}`) was created", "ROLE_DELETE": "🖊 Role **{role.name}** (`{role.id}`) was deleted", - "ROLE_EDIT": "🖊 Role **{role.name}** (`{role.id}`) was edited", + "ROLE_UPDATE": "🖊 Role **{newRole.name}** (`{newRole.id}`) was edited. Changes:\n{differenceString}", "MESSAGE_EDIT": "✏ {userMention(user)} edited their message (`{after.id}`) in {channelMention(channel)}:\n**Before:**{messageSummary(before)}**After:**{messageSummary(after)}", "MESSAGE_DELETE": "🗑 Message (`{message.id}`) from {userMention(user)} deleted in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}", @@ -42,6 +46,18 @@ "VOICE_CHANNEL_FORCE_MOVE": "\uD83C\uDF99 ✍ {userMention(member)} was moved from **{oldChannel.name}** to **{newChannel.name}** by {userMention(mod)}", "VOICE_CHANNEL_FORCE_DISCONNECT": "\uD83C\uDF99 🚫 {userMention(member)} was forcefully disconnected from **{oldChannel.name}** by {userMention(mod)}", + "STAGE_INSTANCE_CREATE": "📣 Stage Instance `{stageInstance.topic}` was created in Stage Channel <#{stageChannel.id}>", + "STAGE_INSTANCE_DELETE": "📣 Stage Instance `{stageInstance.topic}` was deleted in Stage Channel <#{stageChannel.id}>", + "STAGE_INSTANCE_UPDATE": "📣 Stage Instance `{newStageInstance.topic}` was edited in Stage Channel <#{stageChannel.id}>. Changes:\n{differenceString}", + + "EMOJI_CREATE": "<{emoji.identifier}> Emoji `{emoji.name} ({emoji.id})` was created", + "EMOJI_DELETE": "👋 Emoji `{emoji.name} ({emoji.id})` was deleted", + "EMOJI_UPDATE": "<{newEmoji.identifier}> Emoji `{newEmoji.name} ({newEmoji.id})` was updated. Changes:\n{differenceString}", + + "STICKER_CREATE": "🖼️ Sticker `{sticker.name} ({sticker.id})` was created. Description: `{sticker.description}` Format: {emoji.format}", + "STICKER_DELETE": "🖼️ Sticker `{sticker.name} ({sticker.id})` was deleted.", + "STICKER_UPDATE": "🖼️ Sticker `{newSticker.name} ({sticker.id})` was updated. Changes:\n{differenceString}", + "COMMAND": "🤖 {userMention(member)} used command in {channelMention(channel)}:\n`{command}`", "MESSAGE_SPAM_DETECTED": "🛑 {userMention(member)} spam detected in {channelMention(channel)}: {description} (more than {limit} in {interval}s)\n{archiveUrl}", diff --git a/backend/src/data/GuildAntiraidLevels.ts b/backend/src/data/GuildAntiraidLevels.ts index 7f1321c1..5775bc91 100644 --- a/backend/src/data/GuildAntiraidLevels.ts +++ b/backend/src/data/GuildAntiraidLevels.ts @@ -1,5 +1,5 @@ -import { BaseGuildRepository } from "./BaseGuildRepository"; import { getRepository, Repository } from "typeorm"; +import { BaseGuildRepository } from "./BaseGuildRepository"; import { AntiraidLevel } from "./entities/AntiraidLevel"; export class GuildAntiraidLevels extends BaseGuildRepository { diff --git a/backend/src/data/GuildArchives.ts b/backend/src/data/GuildArchives.ts index a67048d5..e55ac905 100644 --- a/backend/src/data/GuildArchives.ts +++ b/backend/src/data/GuildArchives.ts @@ -1,11 +1,12 @@ +import { Guild, Snowflake } from "discord.js"; import moment from "moment-timezone"; -import { ArchiveEntry } from "./entities/ArchiveEntry"; +import { isDefaultSticker } from "src/utils/isDefaultSticker"; import { getRepository, Repository } from "typeorm"; -import { BaseGuildRepository } from "./BaseGuildRepository"; -import { trimLines } from "../utils"; -import { SavedMessage } from "./entities/SavedMessage"; -import { Guild } from "eris"; import { renderTemplate } from "../templateFormatter"; +import { trimLines } from "../utils"; +import { BaseGuildRepository } from "./BaseGuildRepository"; +import { ArchiveEntry } from "./entities/ArchiveEntry"; +import { SavedMessage } from "./entities/SavedMessage"; const DEFAULT_EXPIRY_DAYS = 30; @@ -13,7 +14,7 @@ const MESSAGE_ARCHIVE_HEADER_FORMAT = trimLines(` Server: {guild.name} ({guild.id}) `); const MESSAGE_ARCHIVE_MESSAGE_FORMAT = - "[#{channel.name}] [{user.id}] [{timestamp}] {user.username}#{user.discriminator}: {content}{attachments}"; + "[#{channel.name}] [{user.id}] [{timestamp}] {user.username}#{user.discriminator}: {content}{attachments}{stickers}"; export class GuildArchives extends BaseGuildRepository { protected archives: Repository; @@ -73,13 +74,19 @@ export class GuildArchives extends BaseGuildRepository { protected async renderLinesFromSavedMessages(savedMessages: SavedMessage[], guild: Guild) { const msgLines: string[] = []; for (const msg of savedMessages) { - const channel = guild.channels.get(msg.channel_id); + const channel = guild.channels.cache.get(msg.channel_id as Snowflake); const user = { ...msg.data.author, id: msg.user_id }; const line = await renderTemplate(MESSAGE_ARCHIVE_MESSAGE_FORMAT, { id: msg.id, timestamp: moment.utc(msg.posted_at).format("YYYY-MM-DD HH:mm:ss"), content: msg.data.content, + attachments: msg.data.attachments?.map(att => { + return JSON.stringify({ name: att.name, url: att.url, type: att.contentType }); + }), + stickers: msg.data.stickers?.map(sti => { + return JSON.stringify({ name: sti.name, id: sti.id, isDefault: isDefaultSticker(sti.id) }); + }), user, channel, }); diff --git a/backend/src/data/GuildAutoReactions.ts b/backend/src/data/GuildAutoReactions.ts index 094a1a4a..27fd737d 100644 --- a/backend/src/data/GuildAutoReactions.ts +++ b/backend/src/data/GuildAutoReactions.ts @@ -1,5 +1,5 @@ -import { BaseGuildRepository } from "./BaseGuildRepository"; import { getRepository, Repository } from "typeorm"; +import { BaseGuildRepository } from "./BaseGuildRepository"; import { AutoReaction } from "./entities/AutoReaction"; export class GuildAutoReactions extends BaseGuildRepository { diff --git a/backend/src/data/GuildButtonRoles.ts b/backend/src/data/GuildButtonRoles.ts new file mode 100644 index 00000000..24ee2e59 --- /dev/null +++ b/backend/src/data/GuildButtonRoles.ts @@ -0,0 +1,58 @@ +import { getRepository, Repository } from "typeorm"; +import { BaseGuildRepository } from "./BaseGuildRepository"; +import { ButtonRole } from "./entities/ButtonRole"; + +export class GuildButtonRoles extends BaseGuildRepository { + private buttonRoles: Repository; + + constructor(guildId) { + super(guildId); + this.buttonRoles = getRepository(ButtonRole); + } + + async getForButtonId(buttonId: string) { + return this.buttonRoles.findOne({ + guild_id: this.guildId, + button_id: buttonId, + }); + } + + async getAllForMessageId(messageId: string) { + return this.buttonRoles.find({ + guild_id: this.guildId, + message_id: messageId, + }); + } + + async removeForButtonId(buttonId: string) { + return this.buttonRoles.delete({ + guild_id: this.guildId, + button_id: buttonId, + }); + } + + async removeAllForMessageId(messageId: string) { + return this.buttonRoles.delete({ + guild_id: this.guildId, + message_id: messageId, + }); + } + + async getForButtonGroup(buttonGroup: string) { + return this.buttonRoles.find({ + guild_id: this.guildId, + button_group: buttonGroup, + }); + } + + async add(channelId: string, messageId: string, buttonId: string, buttonGroup: string, buttonName: string) { + await this.buttonRoles.insert({ + guild_id: this.guildId, + channel_id: channelId, + message_id: messageId, + button_id: buttonId, + button_group: buttonGroup, + button_name: buttonName, + }); + } +} diff --git a/backend/src/data/GuildCases.ts b/backend/src/data/GuildCases.ts index 67a1ad41..349b1ce1 100644 --- a/backend/src/data/GuildCases.ts +++ b/backend/src/data/GuildCases.ts @@ -1,11 +1,10 @@ +import { getRepository, In, Repository } from "typeorm"; +import { BaseGuildRepository } from "./BaseGuildRepository"; +import { CaseTypes } from "./CaseTypes"; +import { connection } from "./db"; import { Case } from "./entities/Case"; import { CaseNote } from "./entities/CaseNote"; -import { BaseGuildRepository } from "./BaseGuildRepository"; -import { getRepository, In, Repository } from "typeorm"; -import { DBDateFormat, disableLinkPreviews } from "../utils"; -import { CaseTypes } from "./CaseTypes"; import moment = require("moment-timezone"); -import { connection } from "./db"; const CASE_SUMMARY_REASON_MAX_LENGTH = 300; diff --git a/backend/src/data/GuildContextMenuLinks.ts b/backend/src/data/GuildContextMenuLinks.ts new file mode 100644 index 00000000..76afd3da --- /dev/null +++ b/backend/src/data/GuildContextMenuLinks.ts @@ -0,0 +1,35 @@ +import { DeleteResult, getRepository, InsertResult, Repository } from "typeorm"; +import { BaseGuildRepository } from "./BaseGuildRepository"; +import { ContextMenuLink } from "./entities/ContextMenuLink"; + +export class GuildContextMenuLinks extends BaseGuildRepository { + private contextLinks: Repository; + + constructor(guildId) { + super(guildId); + this.contextLinks = getRepository(ContextMenuLink); + } + + async get(id: string): Promise { + return this.contextLinks.findOne({ + where: { + guild_id: this.guildId, + context_id: id, + }, + }); + } + + async create(contextId: string, contextAction: string): Promise { + return this.contextLinks.insert({ + guild_id: this.guildId, + context_id: contextId, + action_name: contextAction, + }); + } + + async deleteAll(): Promise { + return this.contextLinks.delete({ + guild_id: this.guildId, + }); + } +} diff --git a/backend/src/data/GuildCounters.ts b/backend/src/data/GuildCounters.ts index 4a64aaea..0a1ff57e 100644 --- a/backend/src/data/GuildCounters.ts +++ b/backend/src/data/GuildCounters.ts @@ -1,18 +1,13 @@ -import { BaseGuildRepository } from "./BaseGuildRepository"; -import { FindConditions, getRepository, In, IsNull, LessThan, Not, Repository } from "typeorm"; -import { Counter } from "./entities/Counter"; -import { CounterValue } from "./entities/CounterValue"; -import { - CounterTrigger, - isValidCounterComparisonOp, - TRIGGER_COMPARISON_OPS, - TriggerComparisonOp, -} from "./entities/CounterTrigger"; -import { CounterTriggerState } from "./entities/CounterTriggerState"; import moment from "moment-timezone"; -import { DAYS, DBDateFormat, HOURS, MINUTES } from "../utils"; -import { connection } from "./db"; +import { FindConditions, getRepository, In, IsNull, Not, Repository } from "typeorm"; import { Queue } from "../Queue"; +import { DAYS, DBDateFormat, HOURS, MINUTES } from "../utils"; +import { BaseGuildRepository } from "./BaseGuildRepository"; +import { connection } from "./db"; +import { Counter } from "./entities/Counter"; +import { CounterTrigger, isValidCounterComparisonOp, TriggerComparisonOp } from "./entities/CounterTrigger"; +import { CounterTriggerState } from "./entities/CounterTriggerState"; +import { CounterValue } from "./entities/CounterValue"; const DELETE_UNUSED_COUNTERS_AFTER = 1 * DAYS; const DELETE_UNUSED_COUNTER_TRIGGERS_AFTER = 1 * DAYS; diff --git a/backend/src/data/GuildEvents.ts b/backend/src/data/GuildEvents.ts index 3a2b6489..ead809ab 100644 --- a/backend/src/data/GuildEvents.ts +++ b/backend/src/data/GuildEvents.ts @@ -1,5 +1,5 @@ -import { BaseGuildRepository } from "./BaseGuildRepository"; import { QueuedEventEmitter } from "../QueuedEventEmitter"; +import { BaseGuildRepository } from "./BaseGuildRepository"; export class GuildEvents extends BaseGuildRepository { private queuedEventEmitter: QueuedEventEmitter; diff --git a/backend/src/data/GuildMemberTimezones.ts b/backend/src/data/GuildMemberTimezones.ts index 9e1a1b68..6c4ef82b 100644 --- a/backend/src/data/GuildMemberTimezones.ts +++ b/backend/src/data/GuildMemberTimezones.ts @@ -1,7 +1,7 @@ -import { BaseGuildRepository } from "./BaseGuildRepository"; -import { MemberTimezone } from "./entities/MemberTimezone"; import { getRepository, Repository } from "typeorm/index"; +import { BaseGuildRepository } from "./BaseGuildRepository"; import { connection } from "./db"; +import { MemberTimezone } from "./entities/MemberTimezone"; export class GuildMemberTimezones extends BaseGuildRepository { protected memberTimezones: Repository; diff --git a/backend/src/data/GuildMutes.ts b/backend/src/data/GuildMutes.ts index 93fcfdc5..35f86fc0 100644 --- a/backend/src/data/GuildMutes.ts +++ b/backend/src/data/GuildMutes.ts @@ -1,7 +1,7 @@ import moment from "moment-timezone"; -import { Mute } from "./entities/Mute"; -import { BaseGuildRepository } from "./BaseGuildRepository"; import { Brackets, getRepository, Repository } from "typeorm"; +import { BaseGuildRepository } from "./BaseGuildRepository"; +import { Mute } from "./entities/Mute"; export class GuildMutes extends BaseGuildRepository { private mutes: Repository; diff --git a/backend/src/data/GuildNicknameHistory.ts b/backend/src/data/GuildNicknameHistory.ts index 60f8f419..51ed863c 100644 --- a/backend/src/data/GuildNicknameHistory.ts +++ b/backend/src/data/GuildNicknameHistory.ts @@ -1,9 +1,9 @@ -import { BaseGuildRepository } from "./BaseGuildRepository"; import { getRepository, In, Repository } from "typeorm"; -import { NicknameHistoryEntry } from "./entities/NicknameHistoryEntry"; -import { MINUTES, SECONDS } from "../utils"; import { isAPI } from "../globals"; +import { MINUTES, SECONDS } from "../utils"; +import { BaseGuildRepository } from "./BaseGuildRepository"; import { cleanupNicknames } from "./cleanup/nicknames"; +import { NicknameHistoryEntry } from "./entities/NicknameHistoryEntry"; if (!isAPI()) { const CLEANUP_INTERVAL = 5 * MINUTES; diff --git a/backend/src/data/GuildPersistedData.ts b/backend/src/data/GuildPersistedData.ts index 2120916b..4dd8cffd 100644 --- a/backend/src/data/GuildPersistedData.ts +++ b/backend/src/data/GuildPersistedData.ts @@ -1,6 +1,6 @@ -import { PersistedData } from "./entities/PersistedData"; -import { BaseGuildRepository } from "./BaseGuildRepository"; import { getRepository, Repository } from "typeorm"; +import { BaseGuildRepository } from "./BaseGuildRepository"; +import { PersistedData } from "./entities/PersistedData"; export interface IPartialPersistData { roles?: string[]; diff --git a/backend/src/data/GuildPingableRoles.ts b/backend/src/data/GuildPingableRoles.ts index fa3eda28..c8fd6f05 100644 --- a/backend/src/data/GuildPingableRoles.ts +++ b/backend/src/data/GuildPingableRoles.ts @@ -1,5 +1,5 @@ -import { BaseGuildRepository } from "./BaseGuildRepository"; import { getRepository, Repository } from "typeorm"; +import { BaseGuildRepository } from "./BaseGuildRepository"; import { PingableRole } from "./entities/PingableRole"; export class GuildPingableRoles extends BaseGuildRepository { diff --git a/backend/src/data/GuildReactionRoles.ts b/backend/src/data/GuildReactionRoles.ts index 9b309f03..a3d7e30b 100644 --- a/backend/src/data/GuildReactionRoles.ts +++ b/backend/src/data/GuildReactionRoles.ts @@ -1,6 +1,6 @@ -import { ReactionRole } from "./entities/ReactionRole"; -import { BaseGuildRepository } from "./BaseGuildRepository"; import { getRepository, Repository } from "typeorm"; +import { BaseGuildRepository } from "./BaseGuildRepository"; +import { ReactionRole } from "./entities/ReactionRole"; export class GuildReactionRoles extends BaseGuildRepository { private reactionRoles: Repository; @@ -24,6 +24,9 @@ export class GuildReactionRoles extends BaseGuildRepository { guild_id: this.guildId, message_id: messageId, }, + order: { + order: "ASC", + }, }); } @@ -50,7 +53,14 @@ export class GuildReactionRoles extends BaseGuildRepository { await this.reactionRoles.delete(criteria); } - async add(channelId: string, messageId: string, emoji: string, roleId: string, exclusive?: boolean) { + async add( + channelId: string, + messageId: string, + emoji: string, + roleId: string, + exclusive?: boolean, + position?: number, + ) { await this.reactionRoles.insert({ guild_id: this.guildId, channel_id: channelId, @@ -58,6 +68,7 @@ export class GuildReactionRoles extends BaseGuildRepository { emoji, role_id: roleId, is_exclusive: Boolean(exclusive), + order: position, }); } } diff --git a/backend/src/data/GuildReminders.ts b/backend/src/data/GuildReminders.ts index b370b465..b665569f 100644 --- a/backend/src/data/GuildReminders.ts +++ b/backend/src/data/GuildReminders.ts @@ -1,5 +1,5 @@ -import { BaseGuildRepository } from "./BaseGuildRepository"; import { getRepository, Repository } from "typeorm"; +import { BaseGuildRepository } from "./BaseGuildRepository"; import { Reminder } from "./entities/Reminder"; export class GuildReminders extends BaseGuildRepository { diff --git a/backend/src/data/GuildSavedMessages.ts b/backend/src/data/GuildSavedMessages.ts index 5653fb84..ff4daf7f 100644 --- a/backend/src/data/GuildSavedMessages.ts +++ b/backend/src/data/GuildSavedMessages.ts @@ -1,12 +1,13 @@ -import { getRepository, Repository } from "typeorm"; -import { BaseGuildRepository } from "./BaseGuildRepository"; -import { ISavedMessageData, SavedMessage } from "./entities/SavedMessage"; -import { QueuedEventEmitter } from "../QueuedEventEmitter"; -import { GuildChannel, Message, PossiblyUncachedTextableChannel } from "eris"; +import { GuildChannel, Message } from "discord.js"; import moment from "moment-timezone"; -import { MINUTES, SECONDS } from "../utils"; +import { getRepository, Repository } from "typeorm"; +import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"; import { isAPI } from "../globals"; +import { QueuedEventEmitter } from "../QueuedEventEmitter"; +import { MINUTES, SECONDS } from "../utils"; +import { BaseGuildRepository } from "./BaseGuildRepository"; import { cleanupMessages } from "./cleanup/messages"; +import { ISavedMessageData, SavedMessage } from "./entities/SavedMessage"; if (!isAPI()) { const CLEANUP_INTERVAL = 5 * MINUTES; @@ -34,19 +35,19 @@ export class GuildSavedMessages extends BaseGuildRepository { this.toBePermanent = new Set(); } - public msgToSavedMessageData(msg: Message): ISavedMessageData { + public msgToSavedMessageData(msg: Message): ISavedMessageData { const data: ISavedMessageData = { author: { username: msg.author.username, discriminator: msg.author.discriminator, }, content: msg.content, - timestamp: msg.timestamp, + timestamp: msg.createdTimestamp, }; - if (msg.attachments.length) data.attachments = msg.attachments; + if (msg.attachments.size) data.attachments = [...msg.attachments.values()]; if (msg.embeds.length) data.embeds = msg.embeds; - if (msg.stickers?.length) data.stickers = msg.stickers; + if (msg.stickers?.size) data.stickers = [...msg.stickers.values()]; return data; } @@ -139,12 +140,12 @@ export class GuildSavedMessages extends BaseGuildRepository { this.events.emit(`create:${data.id}`, [inserted]); } - async createFromMsg(msg: Message, overrides = {}) { + async createFromMsg(msg: Message, overrides = {}) { const existingSavedMsg = await this.find(msg.id); if (existingSavedMsg) return; const savedMessageData = this.msgToSavedMessageData(msg); - const postedAt = moment.utc(msg.timestamp, "x").format("YYYY-MM-DD HH:mm:ss"); + const postedAt = moment.utc(msg.createdTimestamp, "x").format("YYYY-MM-DD HH:mm:ss"); const data = { id: msg.id, @@ -159,6 +160,12 @@ export class GuildSavedMessages extends BaseGuildRepository { return this.create({ ...data, ...overrides }); } + async createFromMessages(messages: Message[], overrides = {}) { + for (const msg of messages) { + await this.createFromMsg(msg, overrides); + } + } + async markAsDeleted(id) { await this.messages .createQueryBuilder("messages") @@ -211,10 +218,12 @@ export class GuildSavedMessages extends BaseGuildRepository { const newMessage = { ...oldMessage, data: newData }; + // @ts-ignore await this.messages.update( + // FIXME? { id }, { - data: newData, + data: newData as QueryDeepPartialEntity, }, ); @@ -222,7 +231,7 @@ export class GuildSavedMessages extends BaseGuildRepository { this.events.emit(`update:${id}`, [newMessage, oldMessage]); } - async saveEditFromMsg(msg: Message) { + async saveEditFromMsg(msg: Message) { const newData = this.msgToSavedMessageData(msg); return this.saveEdit(msg.id, newData); } diff --git a/backend/src/data/GuildScheduledPosts.ts b/backend/src/data/GuildScheduledPosts.ts index af621434..c302b81e 100644 --- a/backend/src/data/GuildScheduledPosts.ts +++ b/backend/src/data/GuildScheduledPosts.ts @@ -1,5 +1,5 @@ -import { BaseGuildRepository } from "./BaseGuildRepository"; import { getRepository, Repository } from "typeorm"; +import { BaseGuildRepository } from "./BaseGuildRepository"; import { ScheduledPost } from "./entities/ScheduledPost"; export class GuildScheduledPosts extends BaseGuildRepository { diff --git a/backend/src/data/GuildSlowmodes.ts b/backend/src/data/GuildSlowmodes.ts index 9e4281db..b6452490 100644 --- a/backend/src/data/GuildSlowmodes.ts +++ b/backend/src/data/GuildSlowmodes.ts @@ -1,8 +1,8 @@ -import { BaseGuildRepository } from "./BaseGuildRepository"; +import moment from "moment-timezone"; import { getRepository, Repository } from "typeorm"; +import { BaseGuildRepository } from "./BaseGuildRepository"; import { SlowmodeChannel } from "./entities/SlowmodeChannel"; import { SlowmodeUser } from "./entities/SlowmodeUser"; -import moment from "moment-timezone"; export class GuildSlowmodes extends BaseGuildRepository { private slowmodeChannels: Repository; diff --git a/backend/src/data/GuildStarboardMessages.ts b/backend/src/data/GuildStarboardMessages.ts index c01bb8dc..0e932928 100644 --- a/backend/src/data/GuildStarboardMessages.ts +++ b/backend/src/data/GuildStarboardMessages.ts @@ -1,5 +1,5 @@ -import { BaseGuildRepository } from "./BaseGuildRepository"; import { getRepository, Repository } from "typeorm"; +import { BaseGuildRepository } from "./BaseGuildRepository"; import { StarboardMessage } from "./entities/StarboardMessage"; export class GuildStarboardMessages extends BaseGuildRepository { diff --git a/backend/src/data/GuildStarboardReactions.ts b/backend/src/data/GuildStarboardReactions.ts index 5094491e..413b7120 100644 --- a/backend/src/data/GuildStarboardReactions.ts +++ b/backend/src/data/GuildStarboardReactions.ts @@ -1,5 +1,5 @@ -import { BaseGuildRepository } from "./BaseGuildRepository"; import { getRepository, Repository } from "typeorm"; +import { BaseGuildRepository } from "./BaseGuildRepository"; import { StarboardReaction } from "./entities/StarboardReaction"; export class GuildStarboardReactions extends BaseGuildRepository { diff --git a/backend/src/data/GuildStats.ts b/backend/src/data/GuildStats.ts index a0ca75a6..d70623dd 100644 --- a/backend/src/data/GuildStats.ts +++ b/backend/src/data/GuildStats.ts @@ -1,5 +1,5 @@ -import { BaseGuildRepository } from "./BaseGuildRepository"; import { getRepository, Repository } from "typeorm"; +import { BaseGuildRepository } from "./BaseGuildRepository"; import { StatValue } from "./entities/StatValue"; export class GuildStats extends BaseGuildRepository { diff --git a/backend/src/data/GuildTags.ts b/backend/src/data/GuildTags.ts index 385bfd80..76206164 100644 --- a/backend/src/data/GuildTags.ts +++ b/backend/src/data/GuildTags.ts @@ -1,6 +1,6 @@ -import { Tag } from "./entities/Tag"; import { getRepository, Repository } from "typeorm"; import { BaseGuildRepository } from "./BaseGuildRepository"; +import { Tag } from "./entities/Tag"; import { TagResponse } from "./entities/TagResponse"; export class GuildTags extends BaseGuildRepository { diff --git a/backend/src/data/GuildTempbans.ts b/backend/src/data/GuildTempbans.ts index 76e126c5..f5f52143 100644 --- a/backend/src/data/GuildTempbans.ts +++ b/backend/src/data/GuildTempbans.ts @@ -1,7 +1,6 @@ import moment from "moment-timezone"; -import { Mute } from "./entities/Mute"; +import { getRepository, Repository } from "typeorm"; import { BaseGuildRepository } from "./BaseGuildRepository"; -import { Brackets, getRepository, Repository } from "typeorm"; import { Tempban } from "./entities/Tempban"; export class GuildTempbans extends BaseGuildRepository { diff --git a/backend/src/data/GuildVCAlerts.ts b/backend/src/data/GuildVCAlerts.ts index 6acc35e9..4c64c197 100644 --- a/backend/src/data/GuildVCAlerts.ts +++ b/backend/src/data/GuildVCAlerts.ts @@ -1,5 +1,5 @@ -import { BaseGuildRepository } from "./BaseGuildRepository"; import { getRepository, Repository } from "typeorm"; +import { BaseGuildRepository } from "./BaseGuildRepository"; import { VCAlert } from "./entities/VCAlert"; export class GuildVCAlerts extends BaseGuildRepository { diff --git a/backend/src/data/LogType.ts b/backend/src/data/LogType.ts index bc62cd02..90c81173 100644 --- a/backend/src/data/LogType.ts +++ b/backend/src/data/LogType.ts @@ -18,9 +18,15 @@ export enum LogType { CHANNEL_CREATE, CHANNEL_DELETE, + CHANNEL_UPDATE, + + THREAD_CREATE, + THREAD_DELETE, + THREAD_UPDATE, ROLE_CREATE, ROLE_DELETE, + ROLE_UPDATE, MESSAGE_EDIT, MESSAGE_DELETE, @@ -31,6 +37,18 @@ export enum LogType { VOICE_CHANNEL_LEAVE, VOICE_CHANNEL_MOVE, + STAGE_INSTANCE_CREATE, + STAGE_INSTANCE_DELETE, + STAGE_INSTANCE_UPDATE, + + EMOJI_CREATE, + EMOJI_DELETE, + EMOJI_UPDATE, + + STICKER_CREATE, + STICKER_DELETE, + STICKER_UPDATE, + COMMAND, MESSAGE_SPAM_DETECTED, diff --git a/backend/src/data/Supporters.ts b/backend/src/data/Supporters.ts index 26c294c7..58b1b2d3 100644 --- a/backend/src/data/Supporters.ts +++ b/backend/src/data/Supporters.ts @@ -1,5 +1,5 @@ -import { BaseRepository } from "./BaseRepository"; import { getRepository, Repository } from "typeorm"; +import { BaseRepository } from "./BaseRepository"; import { Supporter } from "./entities/Supporter"; export class Supporters extends BaseRepository { diff --git a/backend/src/data/UsernameHistory.ts b/backend/src/data/UsernameHistory.ts index bcc5647a..c17a747c 100644 --- a/backend/src/data/UsernameHistory.ts +++ b/backend/src/data/UsernameHistory.ts @@ -1,9 +1,9 @@ import { getRepository, In, Repository } from "typeorm"; -import { UsernameHistoryEntry } from "./entities/UsernameHistoryEntry"; +import { isAPI } from "../globals"; import { MINUTES, SECONDS } from "../utils"; import { BaseRepository } from "./BaseRepository"; -import { isAPI } from "../globals"; import { cleanupUsernames } from "./cleanup/usernames"; +import { UsernameHistoryEntry } from "./entities/UsernameHistoryEntry"; if (!isAPI()) { const CLEANUP_INTERVAL = 5 * MINUTES; diff --git a/backend/src/data/cleanup/configs.ts b/backend/src/data/cleanup/configs.ts index 04c76044..252b6c6d 100644 --- a/backend/src/data/cleanup/configs.ts +++ b/backend/src/data/cleanup/configs.ts @@ -1,8 +1,8 @@ -import { connection } from "../db"; -import { getRepository, In } from "typeorm"; -import { Config } from "../entities/Config"; import moment from "moment-timezone"; +import { getRepository, In } from "typeorm"; import { DBDateFormat } from "../../utils"; +import { connection } from "../db"; +import { Config } from "../entities/Config"; const CLEAN_PER_LOOP = 50; diff --git a/backend/src/data/cleanup/messages.ts b/backend/src/data/cleanup/messages.ts index 1b9f8427..06d55c99 100644 --- a/backend/src/data/cleanup/messages.ts +++ b/backend/src/data/cleanup/messages.ts @@ -1,8 +1,8 @@ -import { DAYS, DBDateFormat, MINUTES } from "../../utils"; -import { getRepository, In } from "typeorm"; -import { SavedMessage } from "../entities/SavedMessage"; import moment from "moment-timezone"; +import { getRepository, In } from "typeorm"; +import { DAYS, DBDateFormat, MINUTES } from "../../utils"; import { connection } from "../db"; +import { SavedMessage } from "../entities/SavedMessage"; /** * How long message edits, deletions, etc. will include the original message content. diff --git a/backend/src/data/cleanup/nicknames.ts b/backend/src/data/cleanup/nicknames.ts index e48b2670..4907e004 100644 --- a/backend/src/data/cleanup/nicknames.ts +++ b/backend/src/data/cleanup/nicknames.ts @@ -1,8 +1,8 @@ -import { getRepository, In } from "typeorm"; import moment from "moment-timezone"; -import { NicknameHistoryEntry } from "../entities/NicknameHistoryEntry"; +import { getRepository, In } from "typeorm"; import { DAYS, DBDateFormat } from "../../utils"; import { connection } from "../db"; +import { NicknameHistoryEntry } from "../entities/NicknameHistoryEntry"; export const NICKNAME_RETENTION_PERIOD = 30 * DAYS; const CLEAN_PER_LOOP = 500; diff --git a/backend/src/data/cleanup/usernames.ts b/backend/src/data/cleanup/usernames.ts index 6bcca3d2..eea441d5 100644 --- a/backend/src/data/cleanup/usernames.ts +++ b/backend/src/data/cleanup/usernames.ts @@ -1,8 +1,8 @@ -import { getRepository, In } from "typeorm"; import moment from "moment-timezone"; -import { UsernameHistoryEntry } from "../entities/UsernameHistoryEntry"; +import { getRepository, In } from "typeorm"; import { DAYS, DBDateFormat } from "../../utils"; import { connection } from "../db"; +import { UsernameHistoryEntry } from "../entities/UsernameHistoryEntry"; export const USERNAME_RETENTION_PERIOD = 30 * DAYS; const CLEAN_PER_LOOP = 500; diff --git a/backend/src/data/db.ts b/backend/src/data/db.ts index 30383b14..9888d0b5 100644 --- a/backend/src/data/db.ts +++ b/backend/src/data/db.ts @@ -1,5 +1,5 @@ -import { SimpleError } from "../SimpleError"; import { Connection, createConnection } from "typeorm"; +import { SimpleError } from "../SimpleError"; let connectionPromise: Promise; diff --git a/backend/src/data/encryptedJsonTransformer.ts b/backend/src/data/encryptedJsonTransformer.ts index 01b6080c..38272ebc 100644 --- a/backend/src/data/encryptedJsonTransformer.ts +++ b/backend/src/data/encryptedJsonTransformer.ts @@ -1,5 +1,5 @@ -import { decrypt, encrypt } from "../utils/crypt"; import { ValueTransformer } from "typeorm"; +import { decrypt, encrypt } from "../utils/crypt"; interface EncryptedJsonTransformer extends ValueTransformer { from(dbValue: any): T; diff --git a/backend/src/data/encryptedTextTransformer.ts b/backend/src/data/encryptedTextTransformer.ts index 353c5d72..7d0432ae 100644 --- a/backend/src/data/encryptedTextTransformer.ts +++ b/backend/src/data/encryptedTextTransformer.ts @@ -1,5 +1,5 @@ -import { decrypt, encrypt } from "../utils/crypt"; import { ValueTransformer } from "typeorm"; +import { decrypt, encrypt } from "../utils/crypt"; interface EncryptedTextTransformer extends ValueTransformer { from(dbValue: any): string; diff --git a/backend/src/data/entities/ButtonRole.ts b/backend/src/data/entities/ButtonRole.ts new file mode 100644 index 00000000..3c3f695d --- /dev/null +++ b/backend/src/data/entities/ButtonRole.ts @@ -0,0 +1,24 @@ +import { Column, Entity, PrimaryColumn } from "typeorm"; + +@Entity("button_roles") +export class ButtonRole { + @Column() + @PrimaryColumn() + guild_id: string; + + @Column() + @PrimaryColumn() + channel_id: string; + + @Column() + @PrimaryColumn() + message_id: string; + + @Column() + @PrimaryColumn() + button_id: string; + + @Column() button_group: string; + + @Column() button_name: string; +} diff --git a/backend/src/data/entities/ContextMenuLink.ts b/backend/src/data/entities/ContextMenuLink.ts new file mode 100644 index 00000000..df66fae3 --- /dev/null +++ b/backend/src/data/entities/ContextMenuLink.ts @@ -0,0 +1,10 @@ +import { Column, Entity, PrimaryColumn } from "typeorm"; + +@Entity("context_menus") +export class ContextMenuLink { + @Column() guild_id: string; + + @Column() @PrimaryColumn() context_id: string; + + @Column() action_name: string; +} diff --git a/backend/src/data/entities/CounterTrigger.ts b/backend/src/data/entities/CounterTrigger.ts index 71b6bf52..91cbf995 100644 --- a/backend/src/data/entities/CounterTrigger.ts +++ b/backend/src/data/entities/CounterTrigger.ts @@ -17,7 +17,7 @@ export function getReverseCounterComparisonOp(op: TriggerComparisonOp): TriggerC return REVERSE_OPS[op]; } -const comparisonStringRegex = new RegExp(`^(${TRIGGER_COMPARISON_OPS.join("|")})([1-9]\\d*)$`); +const comparisonStringRegex = new RegExp(`^(${TRIGGER_COMPARISON_OPS.join("|")})(\\d*)$`); /** * @return Parsed comparison op and value, or null if the comparison string was invalid diff --git a/backend/src/data/entities/ReactionRole.ts b/backend/src/data/entities/ReactionRole.ts index 532d3895..198fe0f5 100644 --- a/backend/src/data/entities/ReactionRole.ts +++ b/backend/src/data/entities/ReactionRole.ts @@ -21,4 +21,6 @@ export class ReactionRole { @Column() role_id: string; @Column() is_exclusive: boolean; + + @Column() order: number; } diff --git a/backend/src/data/entities/SavedMessage.ts b/backend/src/data/entities/SavedMessage.ts index ccccf7a2..77294366 100644 --- a/backend/src/data/entities/SavedMessage.ts +++ b/backend/src/data/entities/SavedMessage.ts @@ -1,9 +1,9 @@ +import { MessageAttachment, Sticker } from "discord.js"; import { Column, Entity, PrimaryColumn } from "typeorm"; import { createEncryptedJsonTransformer } from "../encryptedJsonTransformer"; -import { Attachment, Sticker } from "eris"; export interface ISavedMessageData { - attachments?: Attachment[]; + attachments?: MessageAttachment[]; author: { username: string; discriminator: string; diff --git a/backend/src/data/entities/ScheduledPost.ts b/backend/src/data/entities/ScheduledPost.ts index 0d136045..8b7d4088 100644 --- a/backend/src/data/entities/ScheduledPost.ts +++ b/backend/src/data/entities/ScheduledPost.ts @@ -1,5 +1,5 @@ +import { MessageAttachment } from "discord.js"; import { Column, Entity, PrimaryColumn } from "typeorm"; -import { Attachment } from "eris"; import { StrictMessageContent } from "../../utils"; @Entity("scheduled_posts") @@ -18,7 +18,7 @@ export class ScheduledPost { @Column("simple-json") content: StrictMessageContent; - @Column("simple-json") attachments: Attachment[]; + @Column("simple-json") attachments: MessageAttachment[]; @Column({ type: String, nullable: true }) post_at: string | null; diff --git a/backend/src/data/getChannelIdFromMessageId.ts b/backend/src/data/getChannelIdFromMessageId.ts index 64fc3b85..0558ee69 100644 --- a/backend/src/data/getChannelIdFromMessageId.ts +++ b/backend/src/data/getChannelIdFromMessageId.ts @@ -1,5 +1,5 @@ +import { getRepository, Repository } from "typeorm"; import { SavedMessage } from "./entities/SavedMessage"; -import { Repository, getRepository } from "typeorm"; let repository: Repository; diff --git a/backend/src/index.ts b/backend/src/index.ts index 00312b43..7ab1032d 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,32 +1,24 @@ -import "./loadEnv"; - -import path from "path"; +import { Client, Intents, TextChannel } from "discord.js"; import yaml from "js-yaml"; - -import fs from "fs"; import { Knub, PluginError } from "knub"; -import { SimpleError } from "./SimpleError"; - -import { Configs } from "./data/Configs"; +import { PluginLoadError } from "knub/dist/plugins/PluginLoadError"; // Always use UTC internally // This is also enforced for the database in data/db.ts import moment from "moment-timezone"; -import { Client, DiscordHTTPError, TextChannel } from "eris"; -import { connect } from "./data/db"; -import { baseGuildPlugins, globalPlugins, guildPlugins } from "./plugins/availablePlugins"; -import { errorMessage, isDiscordHTTPError, isDiscordRESTError, MINUTES, successMessage } from "./utils"; -import { startUptimeCounter } from "./uptime"; import { AllowedGuilds } from "./data/AllowedGuilds"; -import { ZeppelinGlobalConfig, ZeppelinGuildConfig } from "./types"; -import { RecoverablePluginError } from "./RecoverablePluginError"; +import { Configs } from "./data/Configs"; +import { connect } from "./data/db"; import { GuildLogs } from "./data/GuildLogs"; import { LogType } from "./data/LogType"; -import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin"; +import { DiscordJSError } from "./DiscordJSError"; +import "./loadEnv"; import { logger } from "./logger"; -import { PluginLoadError } from "knub/dist/plugins/PluginLoadError"; -import { ErisError } from "./ErisError"; - -const fsp = fs.promises; +import { baseGuildPlugins, globalPlugins, guildPlugins } from "./plugins/availablePlugins"; +import { RecoverablePluginError } from "./RecoverablePluginError"; +import { SimpleError } from "./SimpleError"; +import { ZeppelinGlobalConfig, ZeppelinGuildConfig } from "./types"; +import { startUptimeCounter } from "./uptime"; +import { errorMessage, isDiscordAPIError, isDiscordHTTPError, successMessage } from "./utils"; if (!process.env.KEY) { // tslint:disable-next-line:no-console @@ -86,7 +78,7 @@ function errorHandler(err) { return; } - if (err instanceof ErisError) { + if (err instanceof DiscordJSError) { if (err.code && SAFE_TO_IGNORE_ERIS_ERROR_CODES.includes(err.code)) { return; } @@ -96,7 +88,7 @@ function errorHandler(err) { } } - if (err instanceof DiscordHTTPError && err.code >= 500) { + if (isDiscordHTTPError(err) && err.code >= 500) { // Don't need stack traces on HTTP 500 errors // These also shouldn't count towards RECENT_DISCORD_ERROR_EXIT_THRESHOLD because they don't indicate an error in our code console.error(err.message); @@ -118,7 +110,7 @@ function errorHandler(err) { console.error(`Exiting after ${RECENT_PLUGIN_ERROR_EXIT_THRESHOLD} plugin errors`); process.exit(1); } - } else if (isDiscordRESTError(err) || isDiscordHTTPError(err)) { + } else if (isDiscordAPIError(err) || isDiscordHTTPError(err)) { // Discord API errors, usually safe to just log instead of crash // We still bail if we get a ton of them in a short amount of time if (++recentDiscordErrors >= RECENT_DISCORD_ERROR_EXIT_THRESHOLD) { @@ -151,48 +143,42 @@ moment.tz.setDefault("UTC"); logger.info("Connecting to database"); connect().then(async () => { - const client = new Client(`Bot ${process.env.TOKEN}`, { - getAllUsers: false, - restMode: true, - compress: false, - guildCreateTimeout: 0, - rest: { - ratelimiterOffset: 150, - }, + const client = new Client({ + partials: ["USER", "CHANNEL", "GUILD_MEMBER", "MESSAGE", "REACTION"], + restTimeOffset: 150, + restGlobalRateLimit: 50, // Disable mentions by default allowedMentions: { - everyone: false, - users: false, - roles: false, + parse: [], + users: [], + roles: [], repliedUser: false, }, intents: [ // Privileged - "guildMembers", - // "guildPresences", - "guildMessageTyping", + Intents.FLAGS.GUILD_MEMBERS, + // Intents.FLAGS.GUILD_PRESENCES, + Intents.FLAGS.GUILD_MESSAGE_TYPING, // Regular - "directMessages", - "guildBans", - "guildEmojis", - "guildInvites", - "guildMessageReactions", - "guildMessages", - "guilds", - "guildVoiceStates", + Intents.FLAGS.DIRECT_MESSAGES, + Intents.FLAGS.GUILD_BANS, + Intents.FLAGS.GUILD_EMOJIS_AND_STICKERS, + Intents.FLAGS.GUILD_INVITES, + Intents.FLAGS.GUILD_MESSAGE_REACTIONS, + Intents.FLAGS.GUILD_MESSAGES, + Intents.FLAGS.GUILDS, + Intents.FLAGS.GUILD_VOICE_STATES, ], }); client.setMaxListeners(200); - client.on("debug", message => { - if (message.includes(" 429 ")) { - logger.info(`[429] ${message}`); - } + client.on("rateLimit", rateLimitData => { + logger.info(`[429] ${JSON.stringify(rateLimitData)}`); }); - client.on("error", (err, shardId) => { - errorHandler(new ErisError(err.message, (err as any).code, shardId)); + client.on("error", err => { + errorHandler(new DiscordJSError(err.message, (err as any).code, 0)); }); const allowedGuilds = new AllowedGuilds(); @@ -257,13 +243,13 @@ connect().then(async () => { sendSuccessMessageFn(channel, body) { const guildId = channel instanceof TextChannel ? channel.guild.id : undefined; const emoji = guildId ? bot.getLoadedGuild(guildId)!.config.success_emoji : undefined; - channel.createMessage(successMessage(body, emoji)); + channel.send(successMessage(body, emoji)); }, sendErrorMessageFn(channel, body) { const guildId = channel instanceof TextChannel ? channel.guild.id : undefined; const emoji = guildId ? bot.getLoadedGuild(guildId)!.config.error_emoji : undefined; - channel.createMessage(errorMessage(body, emoji)); + channel.send(errorMessage(body, emoji)); }, }, }); @@ -272,6 +258,8 @@ connect().then(async () => { startUptimeCounter(); }); - logger.info("Starting the bot"); - bot.run(); + bot.initialize(); + logger.info("Bot Initialized"); + logger.info("Logging in..."); + await client.login(process.env.TOKEN); }); diff --git a/backend/src/migrateConfigsToDB.ts b/backend/src/migrateConfigsToDB.ts index a814a63d..67423c41 100644 --- a/backend/src/migrateConfigsToDB.ts +++ b/backend/src/migrateConfigsToDB.ts @@ -1,8 +1,8 @@ // tslint:disable:no-console -import { connect } from "./data/db"; -import { Configs } from "./data/Configs"; -import path from "path"; import * as _fs from "fs"; +import path from "path"; +import { Configs } from "./data/Configs"; +import { connect } from "./data/db"; const fs = _fs.promises; diff --git a/backend/src/migrations/1608692857722-FixStarboardReactionsIndices.ts b/backend/src/migrations/1608692857722-FixStarboardReactionsIndices.ts index dcde8a09..d74a8749 100644 --- a/backend/src/migrations/1608692857722-FixStarboardReactionsIndices.ts +++ b/backend/src/migrations/1608692857722-FixStarboardReactionsIndices.ts @@ -1,4 +1,4 @@ -import { MigrationInterface, QueryRunner, TableIndex, TableUnique } from "typeorm"; +import { MigrationInterface, QueryRunner, TableIndex } from "typeorm"; export class FixStarboardReactionsIndices1608692857722 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { diff --git a/backend/src/migrations/1622939525343-OrderReactionRoles.ts b/backend/src/migrations/1622939525343-OrderReactionRoles.ts new file mode 100644 index 00000000..2c94b118 --- /dev/null +++ b/backend/src/migrations/1622939525343-OrderReactionRoles.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner, TableColumn } from "typeorm"; + +export class OrderReactionRoles1622939525343 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.addColumn( + "reaction_roles", + new TableColumn({ + name: "order", + type: "int", + isNullable: true, + }), + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumn("reaction_roles", "order"); + } +} diff --git a/backend/src/migrations/1623018101018-CreateButtonRolesTable.ts b/backend/src/migrations/1623018101018-CreateButtonRolesTable.ts new file mode 100644 index 00000000..4d6b47e1 --- /dev/null +++ b/backend/src/migrations/1623018101018-CreateButtonRolesTable.ts @@ -0,0 +1,49 @@ +import { MigrationInterface, QueryRunner, Table } from "typeorm"; + +export class CreateButtonRolesTable1623018101018 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createTable( + new Table({ + name: "button_roles", + columns: [ + { + name: "guild_id", + type: "bigint", + isPrimary: true, + }, + { + name: "channel_id", + type: "bigint", + isPrimary: true, + }, + { + name: "message_id", + type: "bigint", + isPrimary: true, + }, + { + name: "button_id", + type: "varchar", + length: "100", + isPrimary: true, + isUnique: true, + }, + { + name: "button_group", + type: "varchar", + length: "100", + }, + { + name: "button_name", + type: "varchar", + length: "100", + }, + ], + }), + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropTable("button_roles"); + } +} diff --git a/backend/src/migrations/1628809879962-CreateContextMenuTable.ts b/backend/src/migrations/1628809879962-CreateContextMenuTable.ts new file mode 100644 index 00000000..236b7b3c --- /dev/null +++ b/backend/src/migrations/1628809879962-CreateContextMenuTable.ts @@ -0,0 +1,32 @@ +import { MigrationInterface, QueryRunner, Table } from "typeorm"; + +export class CreateContextMenuTable1628809879962 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createTable( + new Table({ + name: "context_menus", + columns: [ + { + name: "guild_id", + type: "bigint", + }, + { + name: "context_id", + type: "bigint", + isPrimary: true, + isUnique: true, + }, + { + name: "action_name", + type: "varchar", + length: "100", + }, + ], + }), + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropTable("context_menus"); + } +} diff --git a/backend/src/pluginUtils.ts b/backend/src/pluginUtils.ts index f1ea4616..14dcf85e 100644 --- a/backend/src/pluginUtils.ts +++ b/backend/src/pluginUtils.ts @@ -2,23 +2,28 @@ * @file Utility functions that are plugin-instance-specific (i.e. use PluginData) */ -import { AdvancedMessageContent, AllowedMentions, GuildTextableChannel, Member, Message, TextableChannel } from "eris"; -import { CommandContext, configUtils, ConfigValidationError, GuildPluginData, helpers, PluginOptions } from "knub"; -import { decodeAndValidateStrict, StrictValidationError, validate } from "./validatorUtils"; -import { deepKeyIntersect, errorMessage, successMessage, tDeepPartial, tNullable } from "./utils"; -import { TZeppelinKnub } from "./types"; -import { ExtendedMatchParams } from "knub/dist/config/PluginConfigManager"; // TODO: Export from Knub index +import { GuildMember, Message, MessageMentionOptions, MessageOptions, TextChannel } from "discord.js"; import * as t from "io-ts"; +import { CommandContext, configUtils, ConfigValidationError, GuildPluginData, helpers, PluginOptions } from "knub"; import { PluginOverrideCriteria } from "knub/dist/config/configTypes"; -import { Tail } from "./utils/typeUtils"; +import { ExtendedMatchParams } from "knub/dist/config/PluginConfigManager"; // TODO: Export from Knub index import { AnyPluginData } from "knub/dist/plugins/PluginData"; -import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin"; import { logger } from "./logger"; +import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin"; +import { TZeppelinKnub } from "./types"; +import { deepKeyIntersect, errorMessage, successMessage, tDeepPartial, tNullable } from "./utils"; +import { Tail } from "./utils/typeUtils"; +import { decodeAndValidateStrict, StrictValidationError, validate } from "./validatorUtils"; const { getMemberLevel } = helpers; -export function canActOn(pluginData: GuildPluginData, member1: Member, member2: Member, allowSameLevel = false) { - if (member2.id === pluginData.client.user.id) { +export function canActOn( + pluginData: GuildPluginData, + member1: GuildMember, + member2: GuildMember, + allowSameLevel = false, +) { + if (member2.id === pluginData.client.user!.id) { return false; } @@ -178,45 +183,43 @@ export function getPluginConfigPreprocessor( }; } -export function sendSuccessMessage( +export async function sendSuccessMessage( pluginData: AnyPluginData, - channel: TextableChannel, + channel: TextChannel, body: string, - allowedMentions?: AllowedMentions, + allowedMentions?: MessageMentionOptions, ): Promise { const emoji = pluginData.fullConfig.success_emoji || undefined; const formattedBody = successMessage(body, emoji); - const content: AdvancedMessageContent = allowedMentions + const content: MessageOptions = allowedMentions ? { content: formattedBody, allowedMentions } : { content: formattedBody }; + return channel - .createMessage(content) // Force line break + .send({ ...content }) // Force line break .catch(err => { - const channelInfo = (channel as GuildTextableChannel).guild - ? `${channel.id} (${(channel as GuildTextableChannel).guild.id})` - : `${channel.id}`; + const channelInfo = channel.guild ? `${channel.id} (${channel.guild.id})` : channel.id; logger.warn(`Failed to send success message to ${channelInfo}): ${err.code} ${err.message}`); return undefined; }); } -export function sendErrorMessage( +export async function sendErrorMessage( pluginData: AnyPluginData, - channel: TextableChannel, + channel: TextChannel, body: string, - allowedMentions?: AllowedMentions, + allowedMentions?: MessageMentionOptions, ): Promise { const emoji = pluginData.fullConfig.error_emoji || undefined; const formattedBody = errorMessage(body, emoji); - const content: AdvancedMessageContent = allowedMentions + const content: MessageOptions = allowedMentions ? { content: formattedBody, allowedMentions } : { content: formattedBody }; + return channel - .createMessage(content) // Force line break + .send({ ...content }) // Force line break .catch(err => { - const channelInfo = (channel as GuildTextableChannel).guild - ? `${channel.id} (${(channel as GuildTextableChannel).guild.id})` - : `${channel.id}`; + const channelInfo = channel.guild ? `${channel.id} (${channel.guild.id})` : channel.id; logger.warn(`Failed to send error message to ${channelInfo}): ${err.code} ${err.message}`); return undefined; }); diff --git a/backend/src/plugins/AutoDelete/AutoDeletePlugin.ts b/backend/src/plugins/AutoDelete/AutoDeletePlugin.ts index 80973641..aa7ffd50 100644 --- a/backend/src/plugins/AutoDelete/AutoDeletePlugin.ts +++ b/backend/src/plugins/AutoDelete/AutoDeletePlugin.ts @@ -1,13 +1,13 @@ import { PluginOptions } from "knub"; -import { AutoDeletePluginType, ConfigSchema } from "./types"; -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildLogs } from "../../data/GuildLogs"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { LogsPlugin } from "../Logs/LogsPlugin"; +import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import { AutoDeletePluginType, ConfigSchema } from "./types"; import { onMessageCreate } from "./util/onMessageCreate"; import { onMessageDelete } from "./util/onMessageDelete"; import { onMessageDeleteBulk } from "./util/onMessageDeleteBulk"; -import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; -import { LogsPlugin } from "../Logs/LogsPlugin"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/AutoDelete/types.ts b/backend/src/plugins/AutoDelete/types.ts index 9cee0d3b..2294f222 100644 --- a/backend/src/plugins/AutoDelete/types.ts +++ b/backend/src/plugins/AutoDelete/types.ts @@ -1,9 +1,9 @@ import * as t from "io-ts"; -import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub"; -import { tDelayString, MINUTES } from "../../utils"; +import { BasePluginType } from "knub"; +import { SavedMessage } from "../../data/entities/SavedMessage"; import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; -import { SavedMessage } from "../../data/entities/SavedMessage"; +import { MINUTES, tDelayString } from "../../utils"; import Timeout = NodeJS.Timeout; export const MAX_DELAY = 5 * MINUTES; diff --git a/backend/src/plugins/AutoDelete/util/addMessageToDeletionQueue.ts b/backend/src/plugins/AutoDelete/util/addMessageToDeletionQueue.ts index 21788d58..7d37c003 100644 --- a/backend/src/plugins/AutoDelete/util/addMessageToDeletionQueue.ts +++ b/backend/src/plugins/AutoDelete/util/addMessageToDeletionQueue.ts @@ -1,8 +1,8 @@ import { GuildPluginData } from "knub"; -import { AutoDeletePluginType } from "../types"; import { SavedMessage } from "../../../data/entities/SavedMessage"; -import { scheduleNextDeletion } from "./scheduleNextDeletion"; import { sorter } from "../../../utils"; +import { AutoDeletePluginType } from "../types"; +import { scheduleNextDeletion } from "./scheduleNextDeletion"; export function addMessageToDeletionQueue( pluginData: GuildPluginData, diff --git a/backend/src/plugins/AutoDelete/util/deleteNextItem.ts b/backend/src/plugins/AutoDelete/util/deleteNextItem.ts index c5ff6d23..332994cf 100644 --- a/backend/src/plugins/AutoDelete/util/deleteNextItem.ts +++ b/backend/src/plugins/AutoDelete/util/deleteNextItem.ts @@ -1,14 +1,15 @@ +import { Permissions, Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { AutoDeletePluginType } from "../types"; import moment from "moment-timezone"; +import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { LogType } from "../../../data/LogType"; -import { resolveUser, stripObjectToScalars, verboseChannelMention } from "../../../utils"; import { logger } from "../../../logger"; -import { scheduleNextDeletion } from "./scheduleNextDeletion"; -import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { resolveUser, verboseChannelMention } from "../../../utils"; import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions"; -import { Constants } from "eris"; import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { AutoDeletePluginType } from "../types"; +import { scheduleNextDeletion } from "./scheduleNextDeletion"; export async function deleteNextItem(pluginData: GuildPluginData) { const [itemToDelete] = pluginData.state.deletionQueue.splice(0, 1); @@ -16,16 +17,16 @@ export async function deleteNextItem(pluginData: GuildPluginData { + (channel as TextChannel).messages.delete(itemToDelete.message.id as Snowflake).catch(err => { if (err.code === 10008) { // "Unknown Message", probably already deleted by automod or another bot, ignore return; @@ -60,8 +61,8 @@ export async function deleteNextItem(pluginData: GuildPluginData, msg: SavedMessage) { diff --git a/backend/src/plugins/AutoDelete/util/onMessageDelete.ts b/backend/src/plugins/AutoDelete/util/onMessageDelete.ts index 98eb2816..48b7bb72 100644 --- a/backend/src/plugins/AutoDelete/util/onMessageDelete.ts +++ b/backend/src/plugins/AutoDelete/util/onMessageDelete.ts @@ -1,6 +1,6 @@ import { GuildPluginData } from "knub"; -import { AutoDeletePluginType } from "../types"; import { SavedMessage } from "../../../data/entities/SavedMessage"; +import { AutoDeletePluginType } from "../types"; import { scheduleNextDeletion } from "./scheduleNextDeletion"; export function onMessageDelete(pluginData: GuildPluginData, msg: SavedMessage) { diff --git a/backend/src/plugins/AutoDelete/util/onMessageDeleteBulk.ts b/backend/src/plugins/AutoDelete/util/onMessageDeleteBulk.ts index b48f8f89..6c39a667 100644 --- a/backend/src/plugins/AutoDelete/util/onMessageDeleteBulk.ts +++ b/backend/src/plugins/AutoDelete/util/onMessageDeleteBulk.ts @@ -1,6 +1,6 @@ -import { AutoDeletePluginType } from "../types"; import { GuildPluginData } from "knub"; import { SavedMessage } from "../../../data/entities/SavedMessage"; +import { AutoDeletePluginType } from "../types"; import { onMessageDelete } from "./onMessageDelete"; export function onMessageDeleteBulk(pluginData: GuildPluginData, messages: SavedMessage[]) { diff --git a/backend/src/plugins/AutoReactions/AutoReactionsPlugin.ts b/backend/src/plugins/AutoReactions/AutoReactionsPlugin.ts index 2192a774..886a9922 100644 --- a/backend/src/plugins/AutoReactions/AutoReactionsPlugin.ts +++ b/backend/src/plugins/AutoReactions/AutoReactionsPlugin.ts @@ -1,13 +1,13 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { AutoReactionsPluginType, ConfigSchema } from "./types"; import { PluginOptions } from "knub"; -import { NewAutoReactionsCmd } from "./commands/NewAutoReactionsCmd"; -import { DisableAutoReactionsCmd } from "./commands/DisableAutoReactionsCmd"; -import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildAutoReactions } from "../../data/GuildAutoReactions"; -import { AddReactionsEvt } from "./events/AddReactionsEvt"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { trimPluginDescription } from "../../utils"; import { LogsPlugin } from "../Logs/LogsPlugin"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import { DisableAutoReactionsCmd } from "./commands/DisableAutoReactionsCmd"; +import { NewAutoReactionsCmd } from "./commands/NewAutoReactionsCmd"; +import { AddReactionsEvt } from "./events/AddReactionsEvt"; +import { AutoReactionsPluginType, ConfigSchema } from "./types"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/AutoReactions/commands/DisableAutoReactionsCmd.ts b/backend/src/plugins/AutoReactions/commands/DisableAutoReactionsCmd.ts index 5f2d82cf..56193797 100644 --- a/backend/src/plugins/AutoReactions/commands/DisableAutoReactionsCmd.ts +++ b/backend/src/plugins/AutoReactions/commands/DisableAutoReactionsCmd.ts @@ -1,6 +1,6 @@ -import { autoReactionsCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { autoReactionsCmd } from "../types"; export const DisableAutoReactionsCmd = autoReactionsCmd({ trigger: "auto_reactions disable", diff --git a/backend/src/plugins/AutoReactions/commands/NewAutoReactionsCmd.ts b/backend/src/plugins/AutoReactions/commands/NewAutoReactionsCmd.ts index 8a4a67e1..2b6c8579 100644 --- a/backend/src/plugins/AutoReactions/commands/NewAutoReactionsCmd.ts +++ b/backend/src/plugins/AutoReactions/commands/NewAutoReactionsCmd.ts @@ -1,13 +1,13 @@ -import { autoReactionsCmd } from "../types"; +import { GuildChannel, Permissions } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canUseEmoji, customEmojiRegex, isEmoji } from "../../../utils"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { canUseEmoji, customEmojiRegex, isEmoji } from "../../../utils"; import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions"; -import { Constants, GuildChannel } from "eris"; -import { readChannelPermissions } from "../../../utils/readChannelPermissions"; import { missingPermissionError } from "../../../utils/missingPermissionError"; +import { readChannelPermissions } from "../../../utils/readChannelPermissions"; +import { autoReactionsCmd } from "../types"; -const requiredPermissions = readChannelPermissions | Constants.Permissions.addReactions; +const requiredPermissions = readChannelPermissions | Permissions.FLAGS.ADD_REACTIONS; export const NewAutoReactionsCmd = autoReactionsCmd({ trigger: "auto_reactions", @@ -22,7 +22,7 @@ export const NewAutoReactionsCmd = autoReactionsCmd({ async run({ message: msg, args, pluginData }) { const finalReactions: string[] = []; - const me = pluginData.guild.members.get(pluginData.client.user.id)!; + const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!; const missingPermissions = getMissingChannelPermissions(me, args.channel as GuildChannel, requiredPermissions); if (missingPermissions) { sendErrorMessage( diff --git a/backend/src/plugins/AutoReactions/events/AddReactionsEvt.ts b/backend/src/plugins/AutoReactions/events/AddReactionsEvt.ts index 54f44951..7f868af1 100644 --- a/backend/src/plugins/AutoReactions/events/AddReactionsEvt.ts +++ b/backend/src/plugins/AutoReactions/events/AddReactionsEvt.ts @@ -1,14 +1,13 @@ -import { autoReactionsEvt } from "../types"; -import { isDiscordRESTError } from "../../../utils"; +import { GuildChannel, Permissions } from "discord.js"; import { LogType } from "../../../data/LogType"; -import { logger } from "../../../logger"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { Constants, GuildChannel } from "eris"; +import { isDiscordAPIError } from "../../../utils"; import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions"; -import { readChannelPermissions } from "../../../utils/readChannelPermissions"; import { missingPermissionError } from "../../../utils/missingPermissionError"; +import { readChannelPermissions } from "../../../utils/readChannelPermissions"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { autoReactionsEvt } from "../types"; -const p = Constants.Permissions; +const p = Permissions.FLAGS; export const AddReactionsEvt = autoReactionsEvt({ event: "messageCreate", @@ -19,11 +18,11 @@ export const AddReactionsEvt = autoReactionsEvt({ const autoReaction = await pluginData.state.autoReactions.getForChannel(message.channel.id); if (!autoReaction) return; - const me = pluginData.guild.members.get(pluginData.client.user.id)!; + const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!; const missingPermissions = getMissingChannelPermissions( me, message.channel as GuildChannel, - readChannelPermissions | p.addReactions, + readChannelPermissions | p.ADD_REACTIONS, ); if (missingPermissions) { const logs = pluginData.getPlugin(LogsPlugin); @@ -35,9 +34,9 @@ export const AddReactionsEvt = autoReactionsEvt({ for (const reaction of autoReaction.reactions) { try { - await message.addReaction(reaction); + await message.react(reaction); } catch (e) { - if (isDiscordRESTError(e)) { + if (isDiscordAPIError(e)) { const logs = pluginData.getPlugin(LogsPlugin); if (e.code === 10008) { logs.log(LogType.BOT_ALERT, { diff --git a/backend/src/plugins/AutoReactions/types.ts b/backend/src/plugins/AutoReactions/types.ts index 6a1fb285..a5c269c2 100644 --- a/backend/src/plugins/AutoReactions/types.ts +++ b/backend/src/plugins/AutoReactions/types.ts @@ -1,8 +1,8 @@ import * as t from "io-ts"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub"; +import { GuildAutoReactions } from "../../data/GuildAutoReactions"; import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; -import { GuildAutoReactions } from "../../data/GuildAutoReactions"; export const ConfigSchema = t.type({ can_manage: t.boolean, diff --git a/backend/src/plugins/Automod/AutomodPlugin.ts b/backend/src/plugins/Automod/AutomodPlugin.ts index fabdc806..eb2484dd 100644 --- a/backend/src/plugins/Automod/AutomodPlugin.ts +++ b/backend/src/plugins/Automod/AutomodPlugin.ts @@ -1,38 +1,35 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { AutomodPluginType, ConfigSchema } from "./types"; -import { RunAutomodOnJoinEvt } from "./events/RunAutomodOnJoinEvt"; -import { GuildLogs } from "../../data/GuildLogs"; -import { GuildSavedMessages } from "../../data/GuildSavedMessages"; -import { runAutomodOnMessage } from "./events/runAutomodOnMessage"; -import { Queue } from "../../Queue"; import { configUtils, CooldownManager } from "knub"; -import { availableTriggers } from "./triggers/availableTriggers"; -import { StrictValidationError } from "../../validatorUtils"; import { ConfigPreprocessorFn } from "knub/dist/config/configTypes"; -import { availableActions } from "./actions/availableActions"; -import { clearOldRecentActions } from "./functions/clearOldRecentActions"; -import { disableCodeBlocks, MINUTES, SECONDS } from "../../utils"; -import { clearOldRecentSpam } from "./functions/clearOldRecentSpam"; import { GuildAntiraidLevels } from "../../data/GuildAntiraidLevels"; import { GuildArchives } from "../../data/GuildArchives"; -import { clearOldRecentNicknameChanges } from "./functions/clearOldNicknameChanges"; +import { GuildLogs } from "../../data/GuildLogs"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { Queue } from "../../Queue"; +import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners"; +import { MINUTES, SECONDS } from "../../utils"; +import { registerEventListenersFromMap } from "../../utils/registerEventListenersFromMap"; +import { unregisterEventListenersFromMap } from "../../utils/unregisterEventListenersFromMap"; +import { StrictValidationError } from "../../validatorUtils"; +import { CountersPlugin } from "../Counters/CountersPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin"; import { ModActionsPlugin } from "../ModActions/ModActionsPlugin"; import { MutesPlugin } from "../Mutes/MutesPlugin"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import { availableActions } from "./actions/availableActions"; import { AntiraidClearCmd } from "./commands/AntiraidClearCmd"; import { SetAntiraidCmd } from "./commands/SetAntiraidCmd"; import { ViewAntiraidCmd } from "./commands/ViewAntiraidCmd"; -import { pluginInfo } from "./info"; -import { RegExpRunner } from "../../RegExpRunner"; -import { LogType } from "../../data/LogType"; -import { logger } from "../../logger"; -import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners"; -import { RunAutomodOnMemberUpdate } from "./events/RunAutomodOnMemberUpdate"; -import { CountersPlugin } from "../Counters/CountersPlugin"; import { runAutomodOnCounterTrigger } from "./events/runAutomodOnCounterTrigger"; +import { RunAutomodOnJoinEvt, RunAutomodOnLeaveEvt } from "./events/RunAutomodOnJoinLeaveEvt"; +import { RunAutomodOnMemberUpdate } from "./events/RunAutomodOnMemberUpdate"; +import { runAutomodOnMessage } from "./events/runAutomodOnMessage"; import { runAutomodOnModAction } from "./events/runAutomodOnModAction"; -import { registerEventListenersFromMap } from "../../utils/registerEventListenersFromMap"; -import { unregisterEventListenersFromMap } from "../../utils/unregisterEventListenersFromMap"; +import { clearOldRecentNicknameChanges } from "./functions/clearOldNicknameChanges"; +import { clearOldRecentActions } from "./functions/clearOldRecentActions"; +import { clearOldRecentSpam } from "./functions/clearOldRecentSpam"; +import { pluginInfo } from "./info"; +import { availableTriggers } from "./triggers/availableTriggers"; +import { AutomodPluginType, ConfigSchema } from "./types"; const defaultOptions = { config: { @@ -179,6 +176,7 @@ export const AutomodPlugin = zeppelinGuildPlugin()({ events: [ RunAutomodOnJoinEvt, RunAutomodOnMemberUpdate, + RunAutomodOnLeaveEvt, // Messages use message events from SavedMessages, see onLoad below ], diff --git a/backend/src/plugins/Automod/actions/addRoles.ts b/backend/src/plugins/Automod/actions/addRoles.ts index 976300a5..91a12bca 100644 --- a/backend/src/plugins/Automod/actions/addRoles.ts +++ b/backend/src/plugins/Automod/actions/addRoles.ts @@ -1,17 +1,16 @@ +import { Permissions, Snowflake } from "discord.js"; import * as t from "io-ts"; -import { automodAction } from "../helpers"; import { LogType } from "../../../data/LogType"; import { nonNullish, unique } from "../../../utils"; -import { Constants } from "eris"; -import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { getMissingPermissions } from "../../../utils/getMissingPermissions"; import { canAssignRole } from "../../../utils/canAssignRole"; -import { missingPermissionError } from "../../../utils/missingPermissionError"; -import { ignoreRoleChange } from "../functions/ignoredRoleChanges"; +import { getMissingPermissions } from "../../../utils/getMissingPermissions"; import { memberRolesLock } from "../../../utils/lockNameHelpers"; +import { missingPermissionError } from "../../../utils/missingPermissionError"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { ignoreRoleChange } from "../functions/ignoredRoleChanges"; +import { automodAction } from "../helpers"; -const p = Constants.Permissions; +const p = Permissions.FLAGS; export const AddRolesAction = automodAction({ configType: t.array(t.string), @@ -19,9 +18,9 @@ export const AddRolesAction = automodAction({ async apply({ pluginData, contexts, actionConfig, ruleName }) { const members = unique(contexts.map(c => c.member).filter(nonNullish)); - const me = pluginData.guild.members.get(pluginData.client.user.id)!; + const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!; - const missingPermissions = getMissingPermissions(me.permission, p.manageRoles); + const missingPermissions = getMissingPermissions(me.permissions, p.MANAGE_ROLES); if (missingPermissions) { const logs = pluginData.getPlugin(LogsPlugin); logs.log(LogType.BOT_ALERT, { @@ -42,7 +41,7 @@ export const AddRolesAction = automodAction({ if (rolesWeCannotAssign.length) { const roleNamesWeCannotAssign = rolesWeCannotAssign.map( - roleId => pluginData.guild.roles.get(roleId)?.name || roleId, + roleId => pluginData.guild.roles.cache.get(roleId as Snowflake)?.name || roleId, ); const logs = pluginData.getPlugin(LogsPlugin); logs.log(LogType.BOT_ALERT, { @@ -54,13 +53,13 @@ export const AddRolesAction = automodAction({ await Promise.all( members.map(async member => { - const memberRoles = new Set(member.roles); + const memberRoles = new Set(member.roles.cache.keys()); for (const roleId of rolesToAssign) { - memberRoles.add(roleId); + memberRoles.add(roleId as Snowflake); ignoreRoleChange(pluginData, member.id, roleId); } - if (memberRoles.size === member.roles.length) { + if (memberRoles.size === member.roles.cache.size) { // No role changes return; } @@ -71,7 +70,6 @@ export const AddRolesAction = automodAction({ await member.edit({ roles: rolesArr, }); - member.roles = rolesArr; // Make sure we know of the new roles internally as well memberRoleLock.unlock(); }), diff --git a/backend/src/plugins/Automod/actions/addToCounter.ts b/backend/src/plugins/Automod/actions/addToCounter.ts index 7606f91d..74e1ddc5 100644 --- a/backend/src/plugins/Automod/actions/addToCounter.ts +++ b/backend/src/plugins/Automod/actions/addToCounter.ts @@ -1,7 +1,7 @@ import * as t from "io-ts"; -import { automodAction } from "../helpers"; -import { CountersPlugin } from "../../Counters/CountersPlugin"; import { LogType } from "../../../data/LogType"; +import { CountersPlugin } from "../../Counters/CountersPlugin"; +import { automodAction } from "../helpers"; export const AddToCounterAction = automodAction({ configType: t.type({ diff --git a/backend/src/plugins/Automod/actions/alert.ts b/backend/src/plugins/Automod/actions/alert.ts index 2076ae55..206c215c 100644 --- a/backend/src/plugins/Automod/actions/alert.ts +++ b/backend/src/plugins/Automod/actions/alert.ts @@ -1,23 +1,18 @@ +import { Snowflake, TextChannel } from "discord.js"; import * as t from "io-ts"; -import { automodAction } from "../helpers"; +import { erisAllowedMentionsToDjsMentionOptions } from "src/utils/erisAllowedMentionsToDjsMentionOptions"; import { LogType } from "../../../data/LogType"; +import { renderTemplate, TemplateParseError } from "../../../templateFormatter"; import { - asyncMap, createChunkedMessage, - isDiscordRESTError, messageLink, - resolveMember, stripObjectToScalars, tAllowedMentions, tNormalizedNullOptional, - tNullable, verboseChannelMention, } from "../../../utils"; -import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; -import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; -import { TextChannel } from "eris"; -import { renderTemplate, TemplateParseError } from "../../../templateFormatter"; import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { automodAction } from "../helpers"; export const AlertAction = automodAction({ configType: t.type({ @@ -29,7 +24,7 @@ export const AlertAction = automodAction({ defaultConfig: {}, async apply({ pluginData, contexts, actionConfig, ruleName, matchResult }) { - const channel = pluginData.guild.channels.get(actionConfig.channel); + const channel = pluginData.guild.channels.cache.get(actionConfig.channel as Snowflake); const logs = pluginData.getPlugin(LogsPlugin); if (channel && channel instanceof TextChannel) { @@ -73,7 +68,11 @@ export const AlertAction = automodAction({ } try { - await createChunkedMessage(channel, rendered, actionConfig.allowed_mentions); + await createChunkedMessage( + channel, + rendered, + erisAllowedMentionsToDjsMentionOptions(actionConfig.allowed_mentions), + ); } catch (err) { if (err.code === 50001) { logs.log(LogType.BOT_ALERT, { diff --git a/backend/src/plugins/Automod/actions/availableActions.ts b/backend/src/plugins/Automod/actions/availableActions.ts index 54146de3..fbdcf8f9 100644 --- a/backend/src/plugins/Automod/actions/availableActions.ts +++ b/backend/src/plugins/Automod/actions/availableActions.ts @@ -1,20 +1,20 @@ import * as t from "io-ts"; -import { CleanAction } from "./clean"; import { AutomodActionBlueprint } from "../helpers"; -import { WarnAction } from "./warn"; -import { MuteAction } from "./mute"; -import { KickAction } from "./kick"; -import { BanAction } from "./ban"; -import { AlertAction } from "./alert"; -import { ChangeNicknameAction } from "./changeNickname"; -import { LogAction } from "./log"; import { AddRolesAction } from "./addRoles"; -import { RemoveRolesAction } from "./removeRoles"; -import { SetAntiraidLevelAction } from "./setAntiraidLevel"; -import { ReplyAction } from "./reply"; import { AddToCounterAction } from "./addToCounter"; +import { AlertAction } from "./alert"; +import { BanAction } from "./ban"; +import { ChangeNicknameAction } from "./changeNickname"; +import { CleanAction } from "./clean"; +import { KickAction } from "./kick"; +import { LogAction } from "./log"; +import { MuteAction } from "./mute"; +import { RemoveRolesAction } from "./removeRoles"; +import { ReplyAction } from "./reply"; +import { SetAntiraidLevelAction } from "./setAntiraidLevel"; import { SetCounterAction } from "./setCounter"; import { SetSlowmodeAction } from "./setSlowmode"; +import { WarnAction } from "./warn"; export const availableActions: Record> = { clean: CleanAction, diff --git a/backend/src/plugins/Automod/actions/ban.ts b/backend/src/plugins/Automod/actions/ban.ts index 4867ead7..ff92be3a 100644 --- a/backend/src/plugins/Automod/actions/ban.ts +++ b/backend/src/plugins/Automod/actions/ban.ts @@ -1,18 +1,9 @@ import * as t from "io-ts"; -import { automodAction } from "../helpers"; -import { LogType } from "../../../data/LogType"; -import { - asyncMap, - convertDelayStringToMS, - nonNullish, - resolveMember, - tDelayString, - tNullable, - unique, -} from "../../../utils"; -import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; -import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; +import { convertDelayStringToMS, nonNullish, tDelayString, tNullable, unique } from "../../../utils"; import { CaseArgs } from "../../Cases/types"; +import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; +import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; +import { automodAction } from "../helpers"; export const BanAction = automodAction({ configType: t.type({ @@ -37,7 +28,7 @@ export const BanAction = automodAction({ const deleteMessageDays = actionConfig.deleteMessageDays || undefined; const caseArgs: Partial = { - modId: pluginData.client.user.id, + modId: pluginData.client.user!.id, extraNotes: matchResult.fullSummary ? [matchResult.fullSummary] : [], automatic: true, postInCaseLogOverride: actionConfig.postInCaseLog ?? undefined, diff --git a/backend/src/plugins/Automod/actions/changeNickname.ts b/backend/src/plugins/Automod/actions/changeNickname.ts index 7225f794..d6dee527 100644 --- a/backend/src/plugins/Automod/actions/changeNickname.ts +++ b/backend/src/plugins/Automod/actions/changeNickname.ts @@ -1,8 +1,8 @@ import * as t from "io-ts"; -import { automodAction } from "../helpers"; import { LogType } from "../../../data/LogType"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; import { nonNullish, unique } from "../../../utils"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { automodAction } from "../helpers"; export const ChangeNicknameAction = automodAction({ configType: t.union([ diff --git a/backend/src/plugins/Automod/actions/clean.ts b/backend/src/plugins/Automod/actions/clean.ts index 3c2c0c7d..56d5be64 100644 --- a/backend/src/plugins/Automod/actions/clean.ts +++ b/backend/src/plugins/Automod/actions/clean.ts @@ -1,7 +1,8 @@ +import { Snowflake, TextChannel } from "discord.js"; import * as t from "io-ts"; -import { automodAction } from "../helpers"; import { LogType } from "../../../data/LogType"; import { noop } from "../../../utils"; +import { automodAction } from "../helpers"; export const CleanAction = automodAction({ configType: t.boolean, @@ -29,7 +30,8 @@ export const CleanAction = automodAction({ pluginData.state.logs.ignoreLog(LogType.MESSAGE_DELETE, id); } - await pluginData.client.deleteMessages(channelId, messageIds).catch(noop); + const channel = pluginData.guild.channels.cache.get(channelId as Snowflake) as TextChannel; + await channel.bulkDelete(messageIds as Snowflake[]).catch(noop); } }, }); diff --git a/backend/src/plugins/Automod/actions/kick.ts b/backend/src/plugins/Automod/actions/kick.ts index 879a541d..98408da3 100644 --- a/backend/src/plugins/Automod/actions/kick.ts +++ b/backend/src/plugins/Automod/actions/kick.ts @@ -1,10 +1,9 @@ import * as t from "io-ts"; -import { automodAction } from "../helpers"; -import { LogType } from "../../../data/LogType"; import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils"; -import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; -import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; import { CaseArgs } from "../../Cases/types"; +import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; +import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; +import { automodAction } from "../helpers"; export const KickAction = automodAction({ configType: t.type({ @@ -25,7 +24,7 @@ export const KickAction = automodAction({ const contactMethods = actionConfig.notify ? resolveActionContactMethods(pluginData, actionConfig) : undefined; const caseArgs: Partial = { - modId: pluginData.client.user.id, + modId: pluginData.client.user!.id, extraNotes: matchResult.fullSummary ? [matchResult.fullSummary] : [], automatic: true, postInCaseLogOverride: actionConfig.postInCaseLog ?? undefined, diff --git a/backend/src/plugins/Automod/actions/log.ts b/backend/src/plugins/Automod/actions/log.ts index 5390fe12..7218ce5f 100644 --- a/backend/src/plugins/Automod/actions/log.ts +++ b/backend/src/plugins/Automod/actions/log.ts @@ -1,8 +1,8 @@ import * as t from "io-ts"; -import { automodAction } from "../helpers"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; import { LogType } from "../../../data/LogType"; import { stripObjectToScalars, unique } from "../../../utils"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { automodAction } from "../helpers"; export const LogAction = automodAction({ configType: t.boolean, diff --git a/backend/src/plugins/Automod/actions/mute.ts b/backend/src/plugins/Automod/actions/mute.ts index 798b8b10..564f65a8 100644 --- a/backend/src/plugins/Automod/actions/mute.ts +++ b/backend/src/plugins/Automod/actions/mute.ts @@ -1,21 +1,12 @@ import * as t from "io-ts"; -import { automodAction } from "../helpers"; import { LogType } from "../../../data/LogType"; -import { - asyncMap, - convertDelayStringToMS, - nonNullish, - resolveMember, - tDelayString, - tNullable, - unique, -} from "../../../utils"; -import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; -import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; -import { MutesPlugin } from "../../Mutes/MutesPlugin"; import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { convertDelayStringToMS, nonNullish, tDelayString, tNullable, unique } from "../../../utils"; import { CaseArgs } from "../../Cases/types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { MutesPlugin } from "../../Mutes/MutesPlugin"; +import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; +import { automodAction } from "../helpers"; export const MuteAction = automodAction({ configType: t.type({ @@ -42,7 +33,7 @@ export const MuteAction = automodAction({ const rolesToRestore = actionConfig.restore_roles_on_mute; const caseArgs: Partial = { - modId: pluginData.client.user.id, + modId: pluginData.client.user!.id, extraNotes: matchResult.fullSummary ? [matchResult.fullSummary] : [], automatic: true, postInCaseLogOverride: actionConfig.postInCaseLog ?? undefined, diff --git a/backend/src/plugins/Automod/actions/removeRoles.ts b/backend/src/plugins/Automod/actions/removeRoles.ts index c6c74e49..0e125fb7 100644 --- a/backend/src/plugins/Automod/actions/removeRoles.ts +++ b/backend/src/plugins/Automod/actions/removeRoles.ts @@ -1,18 +1,16 @@ +import { Permissions, Snowflake } from "discord.js"; import * as t from "io-ts"; -import { automodAction } from "../helpers"; import { LogType } from "../../../data/LogType"; -import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils"; -import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; -import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; -import { getMissingPermissions } from "../../../utils/getMissingPermissions"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { missingPermissionError } from "../../../utils/missingPermissionError"; +import { nonNullish, unique } from "../../../utils"; import { canAssignRole } from "../../../utils/canAssignRole"; -import { Constants } from "eris"; -import { ignoreRoleChange } from "../functions/ignoredRoleChanges"; +import { getMissingPermissions } from "../../../utils/getMissingPermissions"; import { memberRolesLock } from "../../../utils/lockNameHelpers"; +import { missingPermissionError } from "../../../utils/missingPermissionError"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { ignoreRoleChange } from "../functions/ignoredRoleChanges"; +import { automodAction } from "../helpers"; -const p = Constants.Permissions; +const p = Permissions.FLAGS; export const RemoveRolesAction = automodAction({ configType: t.array(t.string), @@ -21,9 +19,9 @@ export const RemoveRolesAction = automodAction({ async apply({ pluginData, contexts, actionConfig, ruleName }) { const members = unique(contexts.map(c => c.member).filter(nonNullish)); - const me = pluginData.guild.members.get(pluginData.client.user.id)!; + const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!; - const missingPermissions = getMissingPermissions(me.permission, p.manageRoles); + const missingPermissions = getMissingPermissions(me.permissions, p.MANAGE_ROLES); if (missingPermissions) { const logs = pluginData.getPlugin(LogsPlugin); logs.log(LogType.BOT_ALERT, { @@ -44,7 +42,7 @@ export const RemoveRolesAction = automodAction({ if (rolesWeCannotRemove.length) { const roleNamesWeCannotRemove = rolesWeCannotRemove.map( - roleId => pluginData.guild.roles.get(roleId)?.name || roleId, + roleId => pluginData.guild.roles.cache.get(roleId as Snowflake)?.name || roleId, ); const logs = pluginData.getPlugin(LogsPlugin); logs.log(LogType.BOT_ALERT, { @@ -56,13 +54,13 @@ export const RemoveRolesAction = automodAction({ await Promise.all( members.map(async member => { - const memberRoles = new Set(member.roles); + const memberRoles = new Set(member.roles.cache.keys()); for (const roleId of rolesToRemove) { - memberRoles.delete(roleId); + memberRoles.delete(roleId as Snowflake); ignoreRoleChange(pluginData, member.id, roleId); } - if (memberRoles.size === member.roles.length) { + if (memberRoles.size === member.roles.cache.size) { // No role changes return; } @@ -73,7 +71,6 @@ export const RemoveRolesAction = automodAction({ await member.edit({ roles: rolesArr, }); - member.roles = rolesArr; // Make sure we know of the new roles internally as well memberRoleLock.unlock(); }), diff --git a/backend/src/plugins/Automod/actions/reply.ts b/backend/src/plugins/Automod/actions/reply.ts index c8291e43..ad682ee9 100644 --- a/backend/src/plugins/Automod/actions/reply.ts +++ b/backend/src/plugins/Automod/actions/reply.ts @@ -1,22 +1,21 @@ +import { MessageOptions, Permissions, Snowflake, TextChannel, User } from "discord.js"; import * as t from "io-ts"; -import { automodAction } from "../helpers"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { LogType } from "../../../data/LogType"; +import { renderTemplate } from "../../../templateFormatter"; import { convertDelayStringToMS, noop, renderRecursively, - StrictMessageContent, - stripObjectToScalars, tDelayString, tMessageContent, tNullable, unique, verboseChannelMention, } from "../../../utils"; -import { AdvancedMessageContent, Constants, MessageContent, TextChannel, User } from "eris"; -import { AutomodContext } from "../types"; -import { renderTemplate } from "../../../templateFormatter"; import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions"; -import { LogType } from "../../../data/LogType"; +import { automodAction } from "../helpers"; +import { AutomodContext } from "../types"; export const ReplyAction = automodAction({ configType: t.union([ @@ -32,7 +31,7 @@ export const ReplyAction = automodAction({ async apply({ pluginData, contexts, actionConfig, ruleName }) { const contextsWithTextChannels = contexts .filter(c => c.message?.channel_id) - .filter(c => pluginData.guild.channels.get(c.message!.channel_id) instanceof TextChannel); + .filter(c => pluginData.guild.channels.cache.get(c.message!.channel_id as Snowflake) instanceof TextChannel); const contextsByChannelId = contextsWithTextChannels.reduce((map: Map, context) => { if (!map.has(context.message!.channel_id)) { @@ -49,21 +48,21 @@ export const ReplyAction = automodAction({ const renderReplyText = async str => renderTemplate(str, { - user: stripObjectToScalars(user), + user: userToConfigAccessibleUser(user), }); const formatted = typeof actionConfig === "string" ? await renderReplyText(actionConfig) - : ((await renderRecursively(actionConfig.text, renderReplyText)) as AdvancedMessageContent); + : ((await renderRecursively(actionConfig.text, renderReplyText)) as MessageOptions); if (formatted) { - const channel = pluginData.guild.channels.get(channelId) as TextChannel; + const channel = pluginData.guild.channels.cache.get(channelId as Snowflake) as TextChannel; // Check for basic Send Messages and View Channel permissions if ( !hasDiscordPermissions( - channel.permissionsOf(pluginData.client.user.id), - Constants.Permissions.sendMessages | Constants.Permissions.readMessages, + channel.permissionsFor(pluginData.client.user!.id), + Permissions.FLAGS.SEND_MESSAGES | Permissions.FLAGS.VIEW_CHANNEL, ) ) { pluginData.state.logs.log(LogType.BOT_ALERT, { @@ -75,7 +74,7 @@ export const ReplyAction = automodAction({ // If the message is an embed, check for embed permissions if ( typeof formatted !== "string" && - !hasDiscordPermissions(channel.permissionsOf(pluginData.client.user.id), Constants.Permissions.embedLinks) + !hasDiscordPermissions(channel.permissionsFor(pluginData.client.user!.id), Permissions.FLAGS.EMBED_LINKS) ) { pluginData.state.logs.log(LogType.BOT_ALERT, { body: `Missing permissions to reply **with an embed** in ${verboseChannelMention( @@ -85,8 +84,8 @@ export const ReplyAction = automodAction({ continue; } - const messageContent: StrictMessageContent = typeof formatted === "string" ? { content: formatted } : formatted; - const replyMsg = await channel.createMessage({ + const messageContent: MessageOptions = typeof formatted === "string" ? { content: formatted } : formatted; + const replyMsg = await channel.send({ ...messageContent, allowedMentions: { users: [user.id], diff --git a/backend/src/plugins/Automod/actions/setAntiraidLevel.ts b/backend/src/plugins/Automod/actions/setAntiraidLevel.ts index 8d15a35f..a1722aae 100644 --- a/backend/src/plugins/Automod/actions/setAntiraidLevel.ts +++ b/backend/src/plugins/Automod/actions/setAntiraidLevel.ts @@ -1,7 +1,7 @@ import * as t from "io-ts"; -import { automodAction } from "../helpers"; -import { setAntiraidLevel } from "../functions/setAntiraidLevel"; import { tNullable } from "../../../utils"; +import { setAntiraidLevel } from "../functions/setAntiraidLevel"; +import { automodAction } from "../helpers"; export const SetAntiraidLevelAction = automodAction({ configType: tNullable(t.string), diff --git a/backend/src/plugins/Automod/actions/setCounter.ts b/backend/src/plugins/Automod/actions/setCounter.ts index eb63d80d..150d77f7 100644 --- a/backend/src/plugins/Automod/actions/setCounter.ts +++ b/backend/src/plugins/Automod/actions/setCounter.ts @@ -1,7 +1,7 @@ import * as t from "io-ts"; -import { automodAction } from "../helpers"; -import { CountersPlugin } from "../../Counters/CountersPlugin"; import { LogType } from "../../../data/LogType"; +import { CountersPlugin } from "../../Counters/CountersPlugin"; +import { automodAction } from "../helpers"; export const SetCounterAction = automodAction({ configType: t.type({ diff --git a/backend/src/plugins/Automod/actions/setSlowmode.ts b/backend/src/plugins/Automod/actions/setSlowmode.ts index 6c69f019..d0d286a8 100644 --- a/backend/src/plugins/Automod/actions/setSlowmode.ts +++ b/backend/src/plugins/Automod/actions/setSlowmode.ts @@ -1,8 +1,9 @@ +import { Snowflake, TextChannel } from "discord.js"; import * as t from "io-ts"; -import { automodAction } from "../helpers"; -import { convertDelayStringToMS, isDiscordRESTError, tDelayString, tNullable } from "../../../utils"; +import { ChannelTypeStrings } from "src/types"; import { LogType } from "../../../data/LogType"; -import { Constants, TextChannel } from "eris"; +import { convertDelayStringToMS, isDiscordAPIError, tDelayString, tNullable } from "../../../utils"; +import { automodAction } from "../helpers"; export const SetSlowmodeAction = automodAction({ configType: t.type({ @@ -18,24 +19,23 @@ export const SetSlowmodeAction = automodAction({ const slowmodeMs = Math.max(actionConfig.duration ? convertDelayStringToMS(actionConfig.duration)! : 0, 0); for (const channelId of actionConfig.channels) { - const channel = pluginData.guild.channels.get(channelId); + const channel = pluginData.guild.channels.cache.get(channelId as Snowflake); // Only text channels and text channels within categories support slowmodes - if ( - !channel || - !(channel.type === Constants.ChannelTypes.GUILD_TEXT || channel.type === Constants.ChannelTypes.GUILD_CATEGORY) - ) { + if (!channel || !(channel.type === ChannelTypeStrings.TEXT || ChannelTypeStrings.CATEGORY)) { continue; } - let channelsToSlowmode: TextChannel[] = []; - if (channel.type === Constants.ChannelTypes.GUILD_CATEGORY) { + const channelsToSlowmode: TextChannel[] = []; + if (channel.type === ChannelTypeStrings.CATEGORY) { // Find all text channels within the category - channelsToSlowmode = pluginData.guild.channels.filter( - ch => ch.parentID === channel.id && ch.type === Constants.ChannelTypes.GUILD_TEXT, - ) as TextChannel[]; + for (const ch of pluginData.guild.channels.cache.values()) { + if (ch.parentId === channel.id && ch.type === ChannelTypeStrings.TEXT) { + channelsToSlowmode.push(ch as TextChannel); + } + } } else { - channelsToSlowmode.push(channel); + channelsToSlowmode.push(channel as TextChannel); } const slowmodeSeconds = Math.ceil(slowmodeMs / 1000); @@ -49,7 +49,7 @@ export const SetSlowmodeAction = automodAction({ } catch (e) { // Check for invalid form body -> indicates duration was too large const errorMessage = - isDiscordRESTError(e) && e.code === 50035 + isDiscordAPIError(e) && e.code === 50035 ? `Duration is greater than maximum native slowmode duration` : e.message; diff --git a/backend/src/plugins/Automod/actions/warn.ts b/backend/src/plugins/Automod/actions/warn.ts index feccf2b0..29dd4955 100644 --- a/backend/src/plugins/Automod/actions/warn.ts +++ b/backend/src/plugins/Automod/actions/warn.ts @@ -1,10 +1,9 @@ import * as t from "io-ts"; -import { automodAction } from "../helpers"; -import { LogType } from "../../../data/LogType"; import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils"; -import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; -import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; import { CaseArgs } from "../../Cases/types"; +import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; +import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; +import { automodAction } from "../helpers"; export const WarnAction = automodAction({ configType: t.type({ @@ -25,7 +24,7 @@ export const WarnAction = automodAction({ const contactMethods = actionConfig.notify ? resolveActionContactMethods(pluginData, actionConfig) : undefined; const caseArgs: Partial = { - modId: pluginData.client.user.id, + modId: pluginData.client.user!.id, extraNotes: matchResult.fullSummary ? [matchResult.fullSummary] : [], automatic: true, postInCaseLogOverride: actionConfig.postInCaseLog ?? undefined, diff --git a/backend/src/plugins/Automod/commands/AntiraidClearCmd.ts b/backend/src/plugins/Automod/commands/AntiraidClearCmd.ts index 8aa210ee..3f119b6b 100644 --- a/backend/src/plugins/Automod/commands/AntiraidClearCmd.ts +++ b/backend/src/plugins/Automod/commands/AntiraidClearCmd.ts @@ -1,7 +1,7 @@ -import { typedGuildCommand, GuildPluginData } from "knub"; -import { AutomodPluginType } from "../types"; -import { setAntiraidLevel } from "../functions/setAntiraidLevel"; +import { typedGuildCommand } from "knub"; import { sendSuccessMessage } from "../../../pluginUtils"; +import { setAntiraidLevel } from "../functions/setAntiraidLevel"; +import { AutomodPluginType } from "../types"; export const AntiraidClearCmd = typedGuildCommand()({ trigger: ["antiraid clear", "antiraid reset", "antiraid none", "antiraid off"], diff --git a/backend/src/plugins/Automod/commands/SetAntiraidCmd.ts b/backend/src/plugins/Automod/commands/SetAntiraidCmd.ts index 0f98ac8a..3ec0f2f9 100644 --- a/backend/src/plugins/Automod/commands/SetAntiraidCmd.ts +++ b/backend/src/plugins/Automod/commands/SetAntiraidCmd.ts @@ -1,8 +1,8 @@ -import { typedGuildCommand, GuildPluginData } from "knub"; -import { AutomodPluginType } from "../types"; -import { setAntiraidLevel } from "../functions/setAntiraidLevel"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { typedGuildCommand } from "knub"; import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { setAntiraidLevel } from "../functions/setAntiraidLevel"; +import { AutomodPluginType } from "../types"; export const SetAntiraidCmd = typedGuildCommand()({ trigger: "antiraid", diff --git a/backend/src/plugins/Automod/commands/ViewAntiraidCmd.ts b/backend/src/plugins/Automod/commands/ViewAntiraidCmd.ts index 76cf64b1..5c208497 100644 --- a/backend/src/plugins/Automod/commands/ViewAntiraidCmd.ts +++ b/backend/src/plugins/Automod/commands/ViewAntiraidCmd.ts @@ -1,8 +1,5 @@ -import { typedGuildCommand, GuildPluginData } from "knub"; +import { typedGuildCommand } from "knub"; import { AutomodPluginType } from "../types"; -import { setAntiraidLevel } from "../functions/setAntiraidLevel"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; export const ViewAntiraidCmd = typedGuildCommand()({ trigger: "antiraid", @@ -10,9 +7,9 @@ export const ViewAntiraidCmd = typedGuildCommand()({ async run({ pluginData, message }) { if (pluginData.state.cachedAntiraidLevel) { - message.channel.createMessage(`Anti-raid is set to **${pluginData.state.cachedAntiraidLevel}**`); + message.channel.send(`Anti-raid is set to **${pluginData.state.cachedAntiraidLevel}**`); } else { - message.channel.createMessage(`Anti-raid is **off**`); + message.channel.send(`Anti-raid is **off**`); } }, }); diff --git a/backend/src/plugins/Automod/constants.ts b/backend/src/plugins/Automod/constants.ts index d6f30b86..1f46f2e3 100644 --- a/backend/src/plugins/Automod/constants.ts +++ b/backend/src/plugins/Automod/constants.ts @@ -15,4 +15,5 @@ export enum RecentActionType { VoiceChannelMove, MemberJoin, Sticker, + MemberLeave, } diff --git a/backend/src/plugins/Automod/events/RunAutomodOnJoinEvt.ts b/backend/src/plugins/Automod/events/RunAutomodOnJoinLeaveEvt.ts similarity index 58% rename from backend/src/plugins/Automod/events/RunAutomodOnJoinEvt.ts rename to backend/src/plugins/Automod/events/RunAutomodOnJoinLeaveEvt.ts index 504a014e..faaff383 100644 --- a/backend/src/plugins/Automod/events/RunAutomodOnJoinEvt.ts +++ b/backend/src/plugins/Automod/events/RunAutomodOnJoinLeaveEvt.ts @@ -1,7 +1,7 @@ import { typedGuildEventListener } from "knub"; -import { AutomodContext, AutomodPluginType } from "../types"; -import { runAutomod } from "../functions/runAutomod"; import { RecentActionType } from "../constants"; +import { runAutomod } from "../functions/runAutomod"; +import { AutomodContext, AutomodPluginType } from "../types"; export const RunAutomodOnJoinEvt = typedGuildEventListener()({ event: "guildMemberAdd", @@ -25,3 +25,25 @@ export const RunAutomodOnJoinEvt = typedGuildEventListener()( }); }, }); + +export const RunAutomodOnLeaveEvt = typedGuildEventListener()({ + event: "guildMemberRemove", + listener({ pluginData, args: { member } }) { + const context: AutomodContext = { + timestamp: Date.now(), + partialMember: member, + joined: true, + }; + + pluginData.state.queue.add(() => { + pluginData.state.recentActions.push({ + type: RecentActionType.MemberLeave, + context, + count: 1, + identifier: null, + }); + + runAutomod(pluginData, context); + }); + }, +}); diff --git a/backend/src/plugins/Automod/events/RunAutomodOnMemberUpdate.ts b/backend/src/plugins/Automod/events/RunAutomodOnMemberUpdate.ts index 275865cf..611aadde 100644 --- a/backend/src/plugins/Automod/events/RunAutomodOnMemberUpdate.ts +++ b/backend/src/plugins/Automod/events/RunAutomodOnMemberUpdate.ts @@ -1,25 +1,24 @@ import { typedGuildEventListener } from "knub"; -import { AutomodContext, AutomodPluginType } from "../types"; -import { RecentActionType } from "../constants"; -import { runAutomod } from "../functions/runAutomod"; -import isEqual from "lodash.isequal"; import diff from "lodash.difference"; +import isEqual from "lodash.isequal"; +import { runAutomod } from "../functions/runAutomod"; +import { AutomodContext, AutomodPluginType } from "../types"; export const RunAutomodOnMemberUpdate = typedGuildEventListener()({ event: "guildMemberUpdate", - listener({ pluginData, args: { member, oldMember } }) { + listener({ pluginData, args: { oldMember, newMember } }) { if (!oldMember) return; - if (isEqual(oldMember.roles, member.roles)) return; + if (isEqual(oldMember.roles, newMember.roles)) return; - const addedRoles = diff(member.roles, oldMember.roles); - const removedRoles = diff(oldMember.roles, member.roles); + const addedRoles = diff(newMember.roles, oldMember.roles); + const removedRoles = diff(oldMember.roles, newMember.roles); if (addedRoles.length || removedRoles.length) { const context: AutomodContext = { timestamp: Date.now(), - user: member.user, - member, + user: newMember.user, + member: newMember, rolesChanged: { added: addedRoles, removed: removedRoles, diff --git a/backend/src/plugins/Automod/events/runAutomodOnAntiraidLevel.ts b/backend/src/plugins/Automod/events/runAutomodOnAntiraidLevel.ts index f36e1067..2dfc4bac 100644 --- a/backend/src/plugins/Automod/events/runAutomodOnAntiraidLevel.ts +++ b/backend/src/plugins/Automod/events/runAutomodOnAntiraidLevel.ts @@ -1,7 +1,7 @@ +import { User } from "discord.js"; import { GuildPluginData } from "knub"; -import { AutomodContext, AutomodPluginType } from "../types"; import { runAutomod } from "../functions/runAutomod"; -import { User } from "eris"; +import { AutomodContext, AutomodPluginType } from "../types"; export async function runAutomodOnAntiraidLevel( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Automod/events/runAutomodOnCounterTrigger.ts b/backend/src/plugins/Automod/events/runAutomodOnCounterTrigger.ts index 2b1b974d..01f75116 100644 --- a/backend/src/plugins/Automod/events/runAutomodOnCounterTrigger.ts +++ b/backend/src/plugins/Automod/events/runAutomodOnCounterTrigger.ts @@ -1,8 +1,8 @@ import { GuildPluginData } from "knub"; -import { AutomodContext, AutomodPluginType } from "../types"; -import { runAutomod } from "../functions/runAutomod"; import { resolveMember, resolveUser, UnknownUser } from "../../../utils"; import { CountersPlugin } from "../../Counters/CountersPlugin"; +import { runAutomod } from "../functions/runAutomod"; +import { AutomodContext, AutomodPluginType } from "../types"; export async function runAutomodOnCounterTrigger( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Automod/events/runAutomodOnMessage.ts b/backend/src/plugins/Automod/events/runAutomodOnMessage.ts index 0ae87a6f..7181b257 100644 --- a/backend/src/plugins/Automod/events/runAutomodOnMessage.ts +++ b/backend/src/plugins/Automod/events/runAutomodOnMessage.ts @@ -1,18 +1,19 @@ -import { SavedMessage } from "../../../data/entities/SavedMessage"; +import { Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; -import { AutomodContext, AutomodPluginType } from "../types"; -import { runAutomod } from "../functions/runAutomod"; -import { addRecentActionsFromMessage } from "../functions/addRecentActionsFromMessage"; import moment from "moment-timezone"; +import { SavedMessage } from "../../../data/entities/SavedMessage"; +import { addRecentActionsFromMessage } from "../functions/addRecentActionsFromMessage"; import { clearRecentActionsForMessage } from "../functions/clearRecentActionsForMessage"; +import { runAutomod } from "../functions/runAutomod"; +import { AutomodContext, AutomodPluginType } from "../types"; export function runAutomodOnMessage( pluginData: GuildPluginData, message: SavedMessage, isEdit: boolean, ) { - const user = pluginData.client.users.get(message.user_id); - const member = pluginData.guild.members.get(message.user_id); + const user = pluginData.client.users.cache.get(message.user_id as Snowflake); + const member = pluginData.guild.members.cache.get(message.user_id as Snowflake); const context: AutomodContext = { timestamp: moment.utc(message.posted_at).valueOf(), diff --git a/backend/src/plugins/Automod/events/runAutomodOnModAction.ts b/backend/src/plugins/Automod/events/runAutomodOnModAction.ts index 5f9437c1..79c8dbea 100644 --- a/backend/src/plugins/Automod/events/runAutomodOnModAction.ts +++ b/backend/src/plugins/Automod/events/runAutomodOnModAction.ts @@ -1,8 +1,8 @@ import { GuildPluginData } from "knub"; -import { AutomodContext, AutomodPluginType } from "../types"; -import { runAutomod } from "../functions/runAutomod"; import { resolveMember, resolveUser, UnknownUser } from "../../../utils"; import { ModActionType } from "../../ModActions/types"; +import { runAutomod } from "../functions/runAutomod"; +import { AutomodContext, AutomodPluginType } from "../types"; export async function runAutomodOnModAction( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Automod/functions/addRecentActionsFromMessage.ts b/backend/src/plugins/Automod/functions/addRecentActionsFromMessage.ts index 90b8dba6..4f533182 100644 --- a/backend/src/plugins/Automod/functions/addRecentActionsFromMessage.ts +++ b/backend/src/plugins/Automod/functions/addRecentActionsFromMessage.ts @@ -1,7 +1,7 @@ -import { AutomodContext, AutomodPluginType } from "../types"; import { GuildPluginData } from "knub"; -import { RECENT_ACTION_EXPIRY_TIME, RecentActionType } from "../constants"; import { getEmojiInString, getRoleMentions, getUrlsInString, getUserMentions } from "../../../utils"; +import { RecentActionType, RECENT_ACTION_EXPIRY_TIME } from "../constants"; +import { AutomodContext, AutomodPluginType } from "../types"; export function addRecentActionsFromMessage(pluginData: GuildPluginData, context: AutomodContext) { const message = context.message!; diff --git a/backend/src/plugins/Automod/functions/checkAndUpdateCooldown.ts b/backend/src/plugins/Automod/functions/checkAndUpdateCooldown.ts index 887d18f0..6338d698 100644 --- a/backend/src/plugins/Automod/functions/checkAndUpdateCooldown.ts +++ b/backend/src/plugins/Automod/functions/checkAndUpdateCooldown.ts @@ -1,7 +1,6 @@ -import { AutomodContext, AutomodPluginType, TRule } from "../types"; import { GuildPluginData } from "knub"; -import { AutomodTriggerMatchResult } from "../helpers"; import { convertDelayStringToMS } from "../../../utils"; +import { AutomodContext, AutomodPluginType, TRule } from "../types"; export function checkAndUpdateCooldown( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Automod/functions/clearOldNicknameChanges.ts b/backend/src/plugins/Automod/functions/clearOldNicknameChanges.ts index 8d668ba7..0d720c2d 100644 --- a/backend/src/plugins/Automod/functions/clearOldNicknameChanges.ts +++ b/backend/src/plugins/Automod/functions/clearOldNicknameChanges.ts @@ -1,6 +1,6 @@ import { GuildPluginData } from "knub"; +import { RECENT_NICKNAME_CHANGE_EXPIRY_TIME } from "../constants"; import { AutomodPluginType } from "../types"; -import { RECENT_NICKNAME_CHANGE_EXPIRY_TIME, RECENT_SPAM_EXPIRY_TIME } from "../constants"; export function clearOldRecentNicknameChanges(pluginData: GuildPluginData) { const now = Date.now(); diff --git a/backend/src/plugins/Automod/functions/clearOldRecentActions.ts b/backend/src/plugins/Automod/functions/clearOldRecentActions.ts index e1ee2fe5..67a79718 100644 --- a/backend/src/plugins/Automod/functions/clearOldRecentActions.ts +++ b/backend/src/plugins/Automod/functions/clearOldRecentActions.ts @@ -1,6 +1,6 @@ import { GuildPluginData } from "knub"; -import { AutomodPluginType } from "../types"; import { RECENT_ACTION_EXPIRY_TIME } from "../constants"; +import { AutomodPluginType } from "../types"; export function clearOldRecentActions(pluginData: GuildPluginData) { const now = Date.now(); diff --git a/backend/src/plugins/Automod/functions/clearOldRecentSpam.ts b/backend/src/plugins/Automod/functions/clearOldRecentSpam.ts index 7fcec63e..a0f34c8b 100644 --- a/backend/src/plugins/Automod/functions/clearOldRecentSpam.ts +++ b/backend/src/plugins/Automod/functions/clearOldRecentSpam.ts @@ -1,6 +1,6 @@ import { GuildPluginData } from "knub"; -import { AutomodPluginType } from "../types"; import { RECENT_SPAM_EXPIRY_TIME } from "../constants"; +import { AutomodPluginType } from "../types"; export function clearOldRecentSpam(pluginData: GuildPluginData) { const now = Date.now(); diff --git a/backend/src/plugins/Automod/functions/clearRecentActionsForMessage.ts b/backend/src/plugins/Automod/functions/clearRecentActionsForMessage.ts index 03af6c9d..7aa9750a 100644 --- a/backend/src/plugins/Automod/functions/clearRecentActionsForMessage.ts +++ b/backend/src/plugins/Automod/functions/clearRecentActionsForMessage.ts @@ -1,7 +1,5 @@ -import { AutomodContext, AutomodPluginType } from "../types"; import { GuildPluginData } from "knub"; -import { RECENT_ACTION_EXPIRY_TIME, RecentActionType } from "../constants"; -import { getEmojiInString, getRoleMentions, getUrlsInString, getUserMentions } from "../../../utils"; +import { AutomodContext, AutomodPluginType } from "../types"; export function clearRecentActionsForMessage(pluginData: GuildPluginData, context: AutomodContext) { const message = context.message!; diff --git a/backend/src/plugins/Automod/functions/createMessageSpamTrigger.ts b/backend/src/plugins/Automod/functions/createMessageSpamTrigger.ts index 09f4af78..009a1f52 100644 --- a/backend/src/plugins/Automod/functions/createMessageSpamTrigger.ts +++ b/backend/src/plugins/Automod/functions/createMessageSpamTrigger.ts @@ -1,13 +1,13 @@ -import { RecentActionType } from "../constants"; -import { automodTrigger } from "../helpers"; +import * as t from "io-ts"; +import { SavedMessage } from "../../../data/entities/SavedMessage"; +import { humanizeDurationShort } from "../../../humanizeDurationShort"; import { getBaseUrl } from "../../../pluginUtils"; import { convertDelayStringToMS, sorter, tDelayString, tNullable } from "../../../utils"; -import { humanizeDurationShort } from "../../../humanizeDurationShort"; +import { RecentActionType } from "../constants"; +import { automodTrigger } from "../helpers"; import { findRecentSpam } from "./findRecentSpam"; import { getMatchingMessageRecentActions } from "./getMatchingMessageRecentActions"; -import * as t from "io-ts"; import { getMessageSpamIdentifier } from "./getSpamIdentifier"; -import { SavedMessage } from "../../../data/entities/SavedMessage"; const MessageSpamTriggerConfig = t.type({ amount: t.number, diff --git a/backend/src/plugins/Automod/functions/findRecentSpam.ts b/backend/src/plugins/Automod/functions/findRecentSpam.ts index 6ec8a59f..0e042ed5 100644 --- a/backend/src/plugins/Automod/functions/findRecentSpam.ts +++ b/backend/src/plugins/Automod/functions/findRecentSpam.ts @@ -1,6 +1,6 @@ import { GuildPluginData } from "knub"; -import { AutomodPluginType } from "../types"; import { RecentActionType } from "../constants"; +import { AutomodPluginType } from "../types"; export function findRecentSpam( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Automod/functions/getMatchingMessageRecentActions.ts b/backend/src/plugins/Automod/functions/getMatchingMessageRecentActions.ts index 7e7299bc..8318ea1e 100644 --- a/backend/src/plugins/Automod/functions/getMatchingMessageRecentActions.ts +++ b/backend/src/plugins/Automod/functions/getMatchingMessageRecentActions.ts @@ -1,9 +1,9 @@ import { GuildPluginData } from "knub"; -import { AutomodPluginType } from "../types"; -import { SavedMessage } from "../../../data/entities/SavedMessage"; import moment from "moment-timezone"; -import { getMatchingRecentActions } from "./getMatchingRecentActions"; +import { SavedMessage } from "../../../data/entities/SavedMessage"; import { RecentActionType } from "../constants"; +import { AutomodPluginType } from "../types"; +import { getMatchingRecentActions } from "./getMatchingRecentActions"; export function getMatchingMessageRecentActions( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Automod/functions/getMatchingRecentActions.ts b/backend/src/plugins/Automod/functions/getMatchingRecentActions.ts index 56d251c2..416c050b 100644 --- a/backend/src/plugins/Automod/functions/getMatchingRecentActions.ts +++ b/backend/src/plugins/Automod/functions/getMatchingRecentActions.ts @@ -1,6 +1,6 @@ import { GuildPluginData } from "knub"; -import { AutomodPluginType } from "../types"; import { RecentActionType } from "../constants"; +import { AutomodPluginType } from "../types"; export function getMatchingRecentActions( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Automod/functions/getTextMatchPartialSummary.ts b/backend/src/plugins/Automod/functions/getTextMatchPartialSummary.ts index 9a86dc13..fe0466e4 100644 --- a/backend/src/plugins/Automod/functions/getTextMatchPartialSummary.ts +++ b/backend/src/plugins/Automod/functions/getTextMatchPartialSummary.ts @@ -1,8 +1,8 @@ -import { MatchableTextType } from "./matchMultipleTextTypesOnMessage"; -import { AutomodContext, AutomodPluginType } from "../types"; -import { messageSummary, verboseChannelMention } from "../../../utils"; +import { Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { User } from "eris"; +import { messageSummary, verboseChannelMention } from "../../../utils"; +import { AutomodContext, AutomodPluginType } from "../types"; +import { MatchableTextType } from "./matchMultipleTextTypesOnMessage"; export function getTextMatchPartialSummary( pluginData: GuildPluginData, @@ -11,24 +11,24 @@ export function getTextMatchPartialSummary( ) { if (type === "message") { const message = context.message!; - const channel = pluginData.guild.channels.get(message.channel_id); + const channel = pluginData.guild.channels.cache.get(message.channel_id as Snowflake) as TextChannel; const channelMention = channel ? verboseChannelMention(channel) : `\`#${message.channel_id}\``; return `message in ${channelMention}:\n${messageSummary(message)}`; } else if (type === "embed") { const message = context.message!; - const channel = pluginData.guild.channels.get(message.channel_id); + const channel = pluginData.guild.channels.cache.get(message.channel_id as Snowflake) as TextChannel; const channelMention = channel ? verboseChannelMention(channel) : `\`#${message.channel_id}\``; return `message embed in ${channelMention}:\n${messageSummary(message)}`; } else if (type === "username") { return `username: ${context.user!.username}`; } else if (type === "nickname") { - return `nickname: ${context.member!.nick}`; + return `nickname: ${context.member!.nickname}`; } else if (type === "visiblename") { - const visibleName = context.member?.nick || context.user!.username; + const visibleName = context.member?.nickname || context.user!.username; return `visible name: ${visibleName}`; } else if (type === "customstatus") { - return `custom status: ${context.member!.game!.state}`; + return `custom status: ${context.member!.presence?.activities.find(a => a.type === "CUSTOM")?.name}`; } } diff --git a/backend/src/plugins/Automod/functions/ignoredRoleChanges.ts b/backend/src/plugins/Automod/functions/ignoredRoleChanges.ts index a3c881a6..a8314a85 100644 --- a/backend/src/plugins/Automod/functions/ignoredRoleChanges.ts +++ b/backend/src/plugins/Automod/functions/ignoredRoleChanges.ts @@ -1,6 +1,6 @@ import { GuildPluginData } from "knub"; -import { AutomodPluginType } from "../types"; import { MINUTES } from "../../../utils"; +import { AutomodPluginType } from "../types"; const IGNORED_ROLE_CHANGE_LIFETIME = 5 * MINUTES; diff --git a/backend/src/plugins/Automod/functions/matchMultipleTextTypesOnMessage.ts b/backend/src/plugins/Automod/functions/matchMultipleTextTypesOnMessage.ts index a51ad74d..10188f7e 100644 --- a/backend/src/plugins/Automod/functions/matchMultipleTextTypesOnMessage.ts +++ b/backend/src/plugins/Automod/functions/matchMultipleTextTypesOnMessage.ts @@ -1,6 +1,7 @@ +import { Constants } from "discord.js"; +import { GuildPluginData } from "knub"; import { SavedMessage } from "../../../data/entities/SavedMessage"; import { resolveMember } from "../../../utils"; -import { GuildPluginData } from "knub"; import { AutomodPluginType } from "../types"; type TextTriggerWithMultipleMatchTypes = { @@ -40,19 +41,21 @@ export async function* matchMultipleTextTypesOnMessage( } if (trigger.match_visible_names) { - yield ["visiblename", member.nick || msg.data.author.username]; + yield ["visiblename", member.nickname || msg.data.author.username]; } if (trigger.match_usernames) { yield ["username", `${msg.data.author.username}#${msg.data.author.discriminator}`]; } - if (trigger.match_nicknames && member.nick) { - yield ["nickname", member.nick]; + if (trigger.match_nicknames && member.nickname) { + yield ["nickname", member.nickname]; } - // type 4 = custom status - if (trigger.match_custom_status && member.game?.type === 4 && member.game?.state) { - yield ["customstatus", member.game.state]; + for (const activity of member.presence?.activities ?? []) { + if (activity.type === Constants.ActivityTypes[4]) { + yield ["customstatus", `${activity.emoji} ${activity.name}`]; + break; + } } } diff --git a/backend/src/plugins/Automod/functions/resolveActionContactMethods.ts b/backend/src/plugins/Automod/functions/resolveActionContactMethods.ts index 2309a95a..8dca9ee4 100644 --- a/backend/src/plugins/Automod/functions/resolveActionContactMethods.ts +++ b/backend/src/plugins/Automod/functions/resolveActionContactMethods.ts @@ -1,7 +1,7 @@ -import { disableUserNotificationStrings, UserNotificationMethod } from "../../../utils"; -import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; -import { TextChannel } from "eris"; +import { Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; +import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; +import { disableUserNotificationStrings, UserNotificationMethod } from "../../../utils"; import { AutomodPluginType } from "../types"; export function resolveActionContactMethods( @@ -18,7 +18,7 @@ export function resolveActionContactMethods( throw new RecoverablePluginError(ERRORS.NO_USER_NOTIFICATION_CHANNEL); } - const channel = pluginData.guild.channels.get(actionConfig.notifyChannel); + const channel = pluginData.guild.channels.cache.get(actionConfig.notifyChannel as Snowflake); if (!(channel instanceof TextChannel)) { throw new RecoverablePluginError(ERRORS.INVALID_USER_NOTIFICATION_CHANNEL); } diff --git a/backend/src/plugins/Automod/functions/runAutomod.ts b/backend/src/plugins/Automod/functions/runAutomod.ts index 0e7ee471..bf22cc5e 100644 --- a/backend/src/plugins/Automod/functions/runAutomod.ts +++ b/backend/src/plugins/Automod/functions/runAutomod.ts @@ -1,19 +1,19 @@ +import { Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { AutomodContext, AutomodPluginType } from "../types"; -import { availableTriggers } from "../triggers/availableTriggers"; import { availableActions } from "../actions/availableActions"; -import { AutomodTriggerMatchResult } from "../helpers"; import { CleanAction } from "../actions/clean"; +import { AutomodTriggerMatchResult } from "../helpers"; +import { availableTriggers } from "../triggers/availableTriggers"; +import { AutomodContext, AutomodPluginType } from "../types"; import { checkAndUpdateCooldown } from "./checkAndUpdateCooldown"; -import { TextChannel } from "eris"; export async function runAutomod(pluginData: GuildPluginData, context: AutomodContext) { const userId = context.user?.id || context.member?.id || context.message?.user_id; - const user = context.user || (userId && pluginData.client.users.get(userId)); - const member = context.member || (userId && pluginData.guild.members.get(userId)) || null; + const user = context.user || (userId && pluginData.client.users!.cache.get(userId as Snowflake)); + const member = context.member || (userId && pluginData.guild.members.cache.get(userId as Snowflake)) || null; const channelId = context.message?.channel_id; - const channel = channelId ? (pluginData.guild.channels.get(channelId) as TextChannel) : null; - const categoryId = channel?.parentID; + const channel = channelId ? (pluginData.guild.channels.cache.get(channelId as Snowflake) as TextChannel) : null; + const categoryId = channel?.parentId; const config = await pluginData.config.getMatchingConfig({ channelId, diff --git a/backend/src/plugins/Automod/functions/setAntiraidLevel.ts b/backend/src/plugins/Automod/functions/setAntiraidLevel.ts index eb72c3ec..c4da61cd 100644 --- a/backend/src/plugins/Automod/functions/setAntiraidLevel.ts +++ b/backend/src/plugins/Automod/functions/setAntiraidLevel.ts @@ -1,10 +1,10 @@ -import { User } from "eris"; +import { User } from "discord.js"; import { GuildPluginData } from "knub"; -import { AutomodPluginType } from "../types"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { LogType } from "../../../data/LogType"; -import { stripObjectToScalars } from "../../../utils"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; import { runAutomodOnAntiraidLevel } from "../events/runAutomodOnAntiraidLevel"; +import { AutomodPluginType } from "../types"; export async function setAntiraidLevel( pluginData: GuildPluginData, @@ -21,7 +21,7 @@ export async function setAntiraidLevel( if (user) { logs.log(LogType.SET_ANTIRAID_USER, { level: newLevel ?? "off", - user: stripObjectToScalars(user), + user: userToConfigAccessibleUser(user), }); } else { logs.log(LogType.SET_ANTIRAID_AUTO, { diff --git a/backend/src/plugins/Automod/helpers.ts b/backend/src/plugins/Automod/helpers.ts index f14e0331..20bafe7e 100644 --- a/backend/src/plugins/Automod/helpers.ts +++ b/backend/src/plugins/Automod/helpers.ts @@ -1,6 +1,6 @@ +import * as t from "io-ts"; import { GuildPluginData } from "knub"; import { Awaitable } from "knub/dist/utils"; -import * as t from "io-ts"; import { AutomodContext, AutomodPluginType } from "./types"; interface BaseAutomodTriggerMatchResult { diff --git a/backend/src/plugins/Automod/info.ts b/backend/src/plugins/Automod/info.ts index 9fcdced0..6a053757 100644 --- a/backend/src/plugins/Automod/info.ts +++ b/backend/src/plugins/Automod/info.ts @@ -1,5 +1,5 @@ -import { ZeppelinGuildPluginBlueprint } from "../ZeppelinPluginBlueprint"; import { trimPluginDescription } from "../../utils"; +import { ZeppelinGuildPluginBlueprint } from "../ZeppelinPluginBlueprint"; export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = { prettyName: "Automod", diff --git a/backend/src/plugins/Automod/triggers/antiraidLevel.ts b/backend/src/plugins/Automod/triggers/antiraidLevel.ts index 98398811..24959c66 100644 --- a/backend/src/plugins/Automod/triggers/antiraidLevel.ts +++ b/backend/src/plugins/Automod/triggers/antiraidLevel.ts @@ -1,6 +1,6 @@ import * as t from "io-ts"; -import { automodTrigger } from "../helpers"; import { tNullable } from "../../../utils"; +import { automodTrigger } from "../helpers"; // tslint:disable-next-line interface AntiraidLevelTriggerResult {} diff --git a/backend/src/plugins/Automod/triggers/anyMessage.ts b/backend/src/plugins/Automod/triggers/anyMessage.ts index 466f0460..9422f4f5 100644 --- a/backend/src/plugins/Automod/triggers/anyMessage.ts +++ b/backend/src/plugins/Automod/triggers/anyMessage.ts @@ -1,6 +1,7 @@ +import { Snowflake, TextChannel } from "discord.js"; import * as t from "io-ts"; -import { automodTrigger } from "../helpers"; import { verboseChannelMention } from "../../../utils"; +import { automodTrigger } from "../helpers"; // tslint:disable-next-line:no-empty-interface interface AnyMessageResultType {} @@ -21,7 +22,7 @@ export const AnyMessageTrigger = automodTrigger()({ }, renderMatchInformation({ pluginData, contexts, matchResult }) { - const channel = pluginData.guild.channels.get(contexts[0].message!.channel_id); + const channel = pluginData.guild.channels.cache.get(contexts[0].message!.channel_id as Snowflake) as TextChannel; return `Matched message (\`${contexts[0].message!.id}\`) in ${ channel ? verboseChannelMention(channel) : "Unknown Channel" }`; diff --git a/backend/src/plugins/Automod/triggers/availableTriggers.ts b/backend/src/plugins/Automod/triggers/availableTriggers.ts index cbb5b7d2..e62e811d 100644 --- a/backend/src/plugins/Automod/triggers/availableTriggers.ts +++ b/backend/src/plugins/Automod/triggers/availableTriggers.ts @@ -1,32 +1,33 @@ import * as t from "io-ts"; -import { MatchWordsTrigger } from "./matchWords"; import { AutomodTriggerBlueprint } from "../helpers"; -import { MessageSpamTrigger } from "./messageSpam"; -import { MentionSpamTrigger } from "./mentionSpam"; -import { LinkSpamTrigger } from "./linkSpam"; +import { AntiraidLevelTrigger } from "./antiraidLevel"; +import { AnyMessageTrigger } from "./anyMessage"; import { AttachmentSpamTrigger } from "./attachmentSpam"; -import { EmojiSpamTrigger } from "./emojiSpam"; -import { LineSpamTrigger } from "./lineSpam"; +import { BanTrigger } from "./ban"; import { CharacterSpamTrigger } from "./characterSpam"; -import { MatchRegexTrigger } from "./matchRegex"; +import { CounterTrigger } from "./counterTrigger"; +import { EmojiSpamTrigger } from "./emojiSpam"; +import { KickTrigger } from "./kick"; +import { LineSpamTrigger } from "./lineSpam"; +import { LinkSpamTrigger } from "./linkSpam"; +import { MatchAttachmentTypeTrigger } from "./matchAttachmentType"; import { MatchInvitesTrigger } from "./matchInvites"; import { MatchLinksTrigger } from "./matchLinks"; -import { MatchAttachmentTypeTrigger } from "./matchAttachmentType"; -import { MemberJoinSpamTrigger } from "./memberJoinSpam"; +import { MatchRegexTrigger } from "./matchRegex"; +import { MatchWordsTrigger } from "./matchWords"; import { MemberJoinTrigger } from "./memberJoin"; +import { MemberJoinSpamTrigger } from "./memberJoinSpam"; +import { MemberLeaveTrigger } from "./memberLeave"; +import { MentionSpamTrigger } from "./mentionSpam"; +import { MessageSpamTrigger } from "./messageSpam"; +import { MuteTrigger } from "./mute"; +import { NoteTrigger } from "./note"; import { RoleAddedTrigger } from "./roleAdded"; import { RoleRemovedTrigger } from "./roleRemoved"; import { StickerSpamTrigger } from "./stickerSpam"; -import { CounterTrigger } from "./counterTrigger"; -import { NoteTrigger } from "./note"; -import { WarnTrigger } from "./warn"; -import { MuteTrigger } from "./mute"; -import { UnmuteTrigger } from "./unmute"; -import { KickTrigger } from "./kick"; -import { BanTrigger } from "./ban"; import { UnbanTrigger } from "./unban"; -import { AnyMessageTrigger } from "./anyMessage"; -import { AntiraidLevelTrigger } from "./antiraidLevel"; +import { UnmuteTrigger } from "./unmute"; +import { WarnTrigger } from "./warn"; export const availableTriggers: Record> = { any_message: AnyMessageTrigger, @@ -72,6 +73,7 @@ export const AvailableTriggers = t.type({ match_links: MatchLinksTrigger.configType, match_attachment_type: MatchAttachmentTypeTrigger.configType, member_join: MemberJoinTrigger.configType, + member_leave: MemberLeaveTrigger.configType, role_added: RoleAddedTrigger.configType, role_removed: RoleRemovedTrigger.configType, diff --git a/backend/src/plugins/Automod/triggers/counterTrigger.ts b/backend/src/plugins/Automod/triggers/counterTrigger.ts index ddfda013..a446164b 100644 --- a/backend/src/plugins/Automod/triggers/counterTrigger.ts +++ b/backend/src/plugins/Automod/triggers/counterTrigger.ts @@ -1,8 +1,6 @@ import * as t from "io-ts"; -import { automodTrigger } from "../helpers"; -import { consumeIgnoredRoleChange } from "../functions/ignoredRoleChanges"; -import { CountersPlugin } from "../../Counters/CountersPlugin"; import { tNullable } from "../../../utils"; +import { automodTrigger } from "../helpers"; // tslint:disable-next-line interface CounterTriggerResult {} diff --git a/backend/src/plugins/Automod/triggers/matchAttachmentType.ts b/backend/src/plugins/Automod/triggers/matchAttachmentType.ts index 45f29697..ba8e4e59 100644 --- a/backend/src/plugins/Automod/triggers/matchAttachmentType.ts +++ b/backend/src/plugins/Automod/triggers/matchAttachmentType.ts @@ -1,12 +1,7 @@ +import { Snowflake, TextChannel, Util } from "discord.js"; import * as t from "io-ts"; +import { asSingleLine, messageSummary, verboseChannelMention } from "../../../utils"; import { automodTrigger } from "../helpers"; -import { - asSingleLine, - disableCodeBlocks, - disableInlineCode, - messageSummary, - verboseChannelMention, -} from "../../../utils"; interface MatchResultType { matchedType: string; @@ -73,12 +68,12 @@ export const MatchAttachmentTypeTrigger = automodTrigger()({ }, renderMatchInformation({ pluginData, contexts, matchResult }) { - const channel = pluginData.guild.channels.get(contexts[0].message!.channel_id)!; + const channel = pluginData.guild.channels.cache.get(contexts[0].message!.channel_id as Snowflake) as TextChannel; const prettyChannel = verboseChannelMention(channel); return ( asSingleLine(` - Matched attachment type \`${disableInlineCode(matchResult.extra.matchedType)}\` + Matched attachment type \`${Util.escapeInlineCode(matchResult.extra.matchedType)}\` (${matchResult.extra.mode === "blacklist" ? "(blacklisted)" : "(not in whitelist)"}) in message (\`${contexts[0].message!.id}\`) in ${prettyChannel}: `) + messageSummary(contexts[0].message!) diff --git a/backend/src/plugins/Automod/triggers/matchInvites.ts b/backend/src/plugins/Automod/triggers/matchInvites.ts index 1e254711..9e0b0c4e 100644 --- a/backend/src/plugins/Automod/triggers/matchInvites.ts +++ b/backend/src/plugins/Automod/triggers/matchInvites.ts @@ -1,17 +1,8 @@ import * as t from "io-ts"; -import { automodTrigger } from "../helpers"; -import { - disableCodeBlocks, - disableInlineCode, - getInviteCodesInString, - GuildInvite, - isGuildInvite, - resolveInvite, - tNullable, - verboseChannelMention, -} from "../../../utils"; -import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage"; +import { getInviteCodesInString, GuildInvite, isGuildInvite, resolveInvite, tNullable } from "../../../utils"; import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary"; +import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage"; +import { automodTrigger } from "../helpers"; interface MatchResultType { type: MatchableTextType; diff --git a/backend/src/plugins/Automod/triggers/matchLinks.ts b/backend/src/plugins/Automod/triggers/matchLinks.ts index 50d953d6..2c70387c 100644 --- a/backend/src/plugins/Automod/triggers/matchLinks.ts +++ b/backend/src/plugins/Automod/triggers/matchLinks.ts @@ -1,18 +1,12 @@ -import * as t from "io-ts"; +import { Util } from "discord.js"; import escapeStringRegexp from "escape-string-regexp"; -import { automodTrigger } from "../helpers"; -import { - asSingleLine, - disableCodeBlocks, - disableInlineCode, - getUrlsInString, - tNullable, - verboseChannelMention, -} from "../../../utils"; -import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage"; +import * as t from "io-ts"; +import { allowTimeout } from "../../../RegExpRunner"; +import { getUrlsInString, tNullable } from "../../../utils"; import { TRegex } from "../../../validatorUtils"; import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary"; -import { allowTimeout } from "../../../RegExpRunner"; +import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage"; +import { automodTrigger } from "../helpers"; interface MatchResultType { type: MatchableTextType; @@ -136,6 +130,6 @@ export const MatchLinksTrigger = automodTrigger()({ renderMatchInformation({ pluginData, contexts, matchResult }) { const partialSummary = getTextMatchPartialSummary(pluginData, matchResult.extra.type, contexts[0]); - return `Matched link \`${disableInlineCode(matchResult.extra.link)}\` in ${partialSummary}`; + return `Matched link \`${Util.escapeInlineCode(matchResult.extra.link)}\` in ${partialSummary}`; }, }); diff --git a/backend/src/plugins/Automod/triggers/matchRegex.ts b/backend/src/plugins/Automod/triggers/matchRegex.ts index 95dc3241..3e0b2e38 100644 --- a/backend/src/plugins/Automod/triggers/matchRegex.ts +++ b/backend/src/plugins/Automod/triggers/matchRegex.ts @@ -1,12 +1,12 @@ +import { Util } from "discord.js"; import * as t from "io-ts"; -import { automodTrigger } from "../helpers"; -import { disableInlineCode, verboseChannelMention } from "../../../utils"; -import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage"; -import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary"; import { allowTimeout } from "../../../RegExpRunner"; -import { TRegex } from "../../../validatorUtils"; import { normalizeText } from "../../../utils/normalizeText"; import { stripMarkdown } from "../../../utils/stripMarkdown"; +import { TRegex } from "../../../validatorUtils"; +import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary"; +import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage"; +import { automodTrigger } from "../helpers"; interface MatchResultType { pattern: string; @@ -72,6 +72,6 @@ export const MatchRegexTrigger = automodTrigger()({ renderMatchInformation({ pluginData, contexts, matchResult }) { const partialSummary = getTextMatchPartialSummary(pluginData, matchResult.extra.type, contexts[0]); - return `Matched regex \`${disableInlineCode(matchResult.extra.pattern)}\` in ${partialSummary}`; + return `Matched regex \`${Util.escapeInlineCode(matchResult.extra.pattern)}\` in ${partialSummary}`; }, }); diff --git a/backend/src/plugins/Automod/triggers/matchWords.ts b/backend/src/plugins/Automod/triggers/matchWords.ts index b5a2da97..387f2208 100644 --- a/backend/src/plugins/Automod/triggers/matchWords.ts +++ b/backend/src/plugins/Automod/triggers/matchWords.ts @@ -1,11 +1,11 @@ -import * as t from "io-ts"; +import { Util } from "discord.js"; import escapeStringRegexp from "escape-string-regexp"; -import { automodTrigger } from "../helpers"; -import { disableInlineCode, verboseChannelMention } from "../../../utils"; -import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage"; -import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary"; +import * as t from "io-ts"; import { normalizeText } from "../../../utils/normalizeText"; import { stripMarkdown } from "../../../utils/stripMarkdown"; +import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary"; +import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage"; +import { automodTrigger } from "../helpers"; interface MatchResultType { word: string; @@ -89,6 +89,6 @@ export const MatchWordsTrigger = automodTrigger()({ renderMatchInformation({ pluginData, contexts, matchResult }) { const partialSummary = getTextMatchPartialSummary(pluginData, matchResult.extra.type, contexts[0]); - return `Matched word \`${disableInlineCode(matchResult.extra.word)}\` in ${partialSummary}`; + return `Matched word \`${Util.escapeInlineCode(matchResult.extra.word)}\` in ${partialSummary}`; }, }); diff --git a/backend/src/plugins/Automod/triggers/memberJoin.ts b/backend/src/plugins/Automod/triggers/memberJoin.ts index 210cd8e5..014c992b 100644 --- a/backend/src/plugins/Automod/triggers/memberJoin.ts +++ b/backend/src/plugins/Automod/triggers/memberJoin.ts @@ -1,6 +1,6 @@ import * as t from "io-ts"; -import { automodTrigger } from "../helpers"; import { convertDelayStringToMS, tDelayString } from "../../../utils"; +import { automodTrigger } from "../helpers"; export const MemberJoinTrigger = automodTrigger()({ configType: t.type({ @@ -20,7 +20,7 @@ export const MemberJoinTrigger = automodTrigger()({ if (triggerConfig.only_new) { const threshold = Date.now() - convertDelayStringToMS(triggerConfig.new_threshold)!; - return context.member.createdAt >= threshold ? {} : null; + return context.member.user.createdTimestamp >= threshold ? {} : null; } return {}; diff --git a/backend/src/plugins/Automod/triggers/memberJoinSpam.ts b/backend/src/plugins/Automod/triggers/memberJoinSpam.ts index f0f9b704..7ab1964a 100644 --- a/backend/src/plugins/Automod/triggers/memberJoinSpam.ts +++ b/backend/src/plugins/Automod/triggers/memberJoinSpam.ts @@ -1,10 +1,10 @@ import * as t from "io-ts"; -import { automodTrigger } from "../helpers"; import { convertDelayStringToMS, tDelayString } from "../../../utils"; -import { getMatchingRecentActions } from "../functions/getMatchingRecentActions"; import { RecentActionType } from "../constants"; -import { sumRecentActionCounts } from "../functions/sumRecentActionCounts"; import { findRecentSpam } from "../functions/findRecentSpam"; +import { getMatchingRecentActions } from "../functions/getMatchingRecentActions"; +import { sumRecentActionCounts } from "../functions/sumRecentActionCounts"; +import { automodTrigger } from "../helpers"; export const MemberJoinSpamTrigger = automodTrigger()({ configType: t.type({ diff --git a/backend/src/plugins/Automod/triggers/memberLeave.ts b/backend/src/plugins/Automod/triggers/memberLeave.ts new file mode 100644 index 00000000..9af9bc4f --- /dev/null +++ b/backend/src/plugins/Automod/triggers/memberLeave.ts @@ -0,0 +1,20 @@ +import * as t from "io-ts"; +import { automodTrigger } from "../helpers"; + +export const MemberLeaveTrigger = automodTrigger()({ + configType: t.type({}), + + defaultConfig: {}, + + async match({ pluginData, context, triggerConfig }) { + if (!context.joined || !context.member) { + return; + } + + return {}; + }, + + renderMatchInformation({ pluginData, contexts, triggerConfig }) { + return ""; + }, +}); diff --git a/backend/src/plugins/Automod/triggers/roleAdded.ts b/backend/src/plugins/Automod/triggers/roleAdded.ts index d7c97171..387f4d9b 100644 --- a/backend/src/plugins/Automod/triggers/roleAdded.ts +++ b/backend/src/plugins/Automod/triggers/roleAdded.ts @@ -1,6 +1,7 @@ +import { Snowflake } from "discord.js"; import * as t from "io-ts"; -import { automodTrigger } from "../helpers"; import { consumeIgnoredRoleChange } from "../functions/ignoredRoleChanges"; +import { automodTrigger } from "../helpers"; interface RoleAddedMatchResult { matchedRoleId: string; @@ -33,10 +34,10 @@ export const RoleAddedTrigger = automodTrigger()({ }, renderMatchInformation({ matchResult, pluginData, contexts }) { - const role = pluginData.guild.roles.get(matchResult.extra.matchedRoleId); + const role = pluginData.guild.roles.cache.get(matchResult.extra.matchedRoleId as Snowflake); const roleName = role?.name || "Unknown"; const member = contexts[0].member!; - const memberName = `**${member.user.username}#${member.user.discriminator}** (\`${member.id}\`)`; + const memberName = `**${member.user.tag}** (\`${member.id}\`)`; return `Role ${roleName} (\`${matchResult.extra.matchedRoleId}\`) was added to ${memberName}`; }, }); diff --git a/backend/src/plugins/Automod/triggers/roleRemoved.ts b/backend/src/plugins/Automod/triggers/roleRemoved.ts index 1613f91d..46b68376 100644 --- a/backend/src/plugins/Automod/triggers/roleRemoved.ts +++ b/backend/src/plugins/Automod/triggers/roleRemoved.ts @@ -1,6 +1,7 @@ +import { Snowflake } from "discord.js"; import * as t from "io-ts"; -import { automodTrigger } from "../helpers"; import { consumeIgnoredRoleChange } from "../functions/ignoredRoleChanges"; +import { automodTrigger } from "../helpers"; interface RoleAddedMatchResult { matchedRoleId: string; @@ -33,10 +34,10 @@ export const RoleRemovedTrigger = automodTrigger()({ }, renderMatchInformation({ matchResult, pluginData, contexts }) { - const role = pluginData.guild.roles.get(matchResult.extra.matchedRoleId); + const role = pluginData.guild.roles.cache.get(matchResult.extra.matchedRoleId as Snowflake); const roleName = role?.name || "Unknown"; const member = contexts[0].member!; - const memberName = `**${member.user.username}#${member.user.discriminator}** (\`${member.id}\`)`; + const memberName = `**${member.user.tag}** (\`${member.id}\`)`; return `Role ${roleName} (\`${matchResult.extra.matchedRoleId}\`) was removed from ${memberName}`; }, }); diff --git a/backend/src/plugins/Automod/types.ts b/backend/src/plugins/Automod/types.ts index cd7ebacf..3981860d 100644 --- a/backend/src/plugins/Automod/types.ts +++ b/backend/src/plugins/Automod/types.ts @@ -1,21 +1,22 @@ +import { GuildMember, PartialGuildMember, User } from "discord.js"; import * as t from "io-ts"; -import { tNullable, UnknownUser } from "../../utils"; import { BasePluginType, CooldownManager } from "knub"; -import { GuildSavedMessages } from "../../data/GuildSavedMessages"; -import { GuildLogs } from "../../data/GuildLogs"; import { SavedMessage } from "../../data/entities/SavedMessage"; -import { Member, User } from "eris"; -import { AvailableTriggers } from "./triggers/availableTriggers"; -import { AvailableActions } from "./actions/availableActions"; -import { Queue } from "../../Queue"; import { GuildAntiraidLevels } from "../../data/GuildAntiraidLevels"; import { GuildArchives } from "../../data/GuildArchives"; -import { RecentActionType } from "./constants"; -import Timeout = NodeJS.Timeout; +import { GuildLogs } from "../../data/GuildLogs"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { Queue } from "../../Queue"; import { RegExpRunner } from "../../RegExpRunner"; +import { tNullable } from "../../utils"; import { CounterEvents } from "../Counters/types"; import { ModActionsEvents, ModActionType } from "../ModActions/types"; import { MutesEvents } from "../Mutes/types"; +import { AvailableActions } from "./actions/availableActions"; +import { RecentActionType } from "./constants"; +import { AvailableTriggers } from "./triggers/availableTriggers"; + +import Timeout = NodeJS.Timeout; export const Rule = t.type({ enabled: t.boolean, @@ -113,7 +114,8 @@ export interface AutomodContext { }; user?: User; message?: SavedMessage; - member?: Member; + member?: GuildMember; + partialMember?: GuildMember | PartialGuildMember; joined?: boolean; rolesChanged?: { added?: string[]; diff --git a/backend/src/plugins/BotControl/BotControlPlugin.ts b/backend/src/plugins/BotControl/BotControlPlugin.ts index e8151be1..dd53d72c 100644 --- a/backend/src/plugins/BotControl/BotControlPlugin.ts +++ b/backend/src/plugins/BotControl/BotControlPlugin.ts @@ -1,23 +1,23 @@ -import { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint"; -import { BotControlPluginType, ConfigSchema } from "./types"; -import { GuildArchives } from "../../data/GuildArchives"; -import { TextChannel } from "eris"; -import { sendSuccessMessage } from "../../pluginUtils"; -import { getActiveReload, resetActiveReload } from "./activeReload"; -import { ReloadGlobalPluginsCmd } from "./commands/ReloadGlobalPluginsCmd"; -import { ServersCmd } from "./commands/ServersCmd"; -import { LeaveServerCmd } from "./commands/LeaveServerCmd"; -import { ReloadServerCmd } from "./commands/ReloadServerCmd"; +import { Snowflake, TextChannel } from "discord.js"; import { AllowedGuilds } from "../../data/AllowedGuilds"; +import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments"; +import { Configs } from "../../data/Configs"; +import { GuildArchives } from "../../data/GuildArchives"; +import { sendSuccessMessage } from "../../pluginUtils"; +import { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint"; +import { getActiveReload, resetActiveReload } from "./activeReload"; +import { AddDashboardUserCmd } from "./commands/AddDashboardUserCmd"; import { AllowServerCmd } from "./commands/AllowServerCmd"; import { DisallowServerCmd } from "./commands/DisallowServerCmd"; -import { AddDashboardUserCmd } from "./commands/AddDashboardUserCmd"; -import { RemoveDashboardUserCmd } from "./commands/RemoveDashboardUserCmd"; -import { Configs } from "../../data/Configs"; -import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments"; -import { ListDashboardUsersCmd } from "./commands/ListDashboardUsersCmd"; -import { ListDashboardPermsCmd } from "./commands/ListDashboardPermsCmd"; import { EligibleCmd } from "./commands/EligibleCmd"; +import { LeaveServerCmd } from "./commands/LeaveServerCmd"; +import { ListDashboardPermsCmd } from "./commands/ListDashboardPermsCmd"; +import { ListDashboardUsersCmd } from "./commands/ListDashboardUsersCmd"; +import { ReloadGlobalPluginsCmd } from "./commands/ReloadGlobalPluginsCmd"; +import { ReloadServerCmd } from "./commands/ReloadServerCmd"; +import { RemoveDashboardUserCmd } from "./commands/RemoveDashboardUserCmd"; +import { ServersCmd } from "./commands/ServersCmd"; +import { BotControlPluginType, ConfigSchema } from "./types"; const defaultOptions = { config: { @@ -47,7 +47,7 @@ export const BotControlPlugin = zeppelinGlobalPlugin()({ EligibleCmd, ], - afterLoad(pluginData) { + async afterLoad(pluginData) { pluginData.state.archives = new GuildArchives(0); pluginData.state.allowedGuilds = new AllowedGuilds(); pluginData.state.configs = new Configs(); @@ -58,9 +58,9 @@ export const BotControlPlugin = zeppelinGlobalPlugin()({ const [guildId, channelId] = activeReload; resetActiveReload(); - const guild = pluginData.client.guilds.get(guildId); + const guild = await pluginData.client.guilds.fetch(guildId as Snowflake); if (guild) { - const channel = guild.channels.get(channelId); + const channel = guild.channels.cache.get(channelId as Snowflake); if (channel instanceof TextChannel) { sendSuccessMessage(pluginData, channel, "Global plugins reloaded!"); } diff --git a/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts b/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts index 5bda2423..84cce432 100644 --- a/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts +++ b/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts @@ -1,7 +1,8 @@ -import { botControlCmd } from "../types"; -import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; import { ApiPermissions } from "@shared/apiPermissions"; +import { TextChannel } from "discord.js"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { botControlCmd } from "../types"; export const AddDashboardUserCmd = botControlCmd({ trigger: ["add_dashboard_user"], @@ -18,7 +19,7 @@ export const AddDashboardUserCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const guild = await pluginData.state.allowedGuilds.find(args.guildId); if (!guild) { - sendErrorMessage(pluginData, msg.channel, "Server is not using Zeppelin"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "Server is not using Zeppelin"); return; } @@ -34,12 +35,10 @@ export const AddDashboardUserCmd = botControlCmd({ await pluginData.state.apiPermissionAssignments.addUser(args.guildId, user.id, [ApiPermissions.EditConfig]); } - const userNameList = args.users.map( - user => `<@!${user.id}> (**${user.username}#${user.discriminator}**, \`${user.id}\`)`, - ); + const userNameList = args.users.map(user => `<@!${user.id}> (**${user.tag}**, \`${user.id}\`)`); sendSuccessMessage( pluginData, - msg.channel, + msg.channel as TextChannel, `The following users were given dashboard access for **${guild.name}**:\n\n${userNameList}`, ); }, diff --git a/backend/src/plugins/BotControl/commands/AllowServerCmd.ts b/backend/src/plugins/BotControl/commands/AllowServerCmd.ts index 7d969f8f..2ff34f6d 100644 --- a/backend/src/plugins/BotControl/commands/AllowServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/AllowServerCmd.ts @@ -1,8 +1,9 @@ -import { botControlCmd } from "../types"; -import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { isSnowflake } from "../../../utils"; import { ApiPermissions } from "@shared/apiPermissions"; +import { TextChannel } from "discord.js"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { isSnowflake } from "../../../utils"; +import { botControlCmd } from "../types"; export const AllowServerCmd = botControlCmd({ trigger: ["allow_server", "allowserver", "add_server", "addserver"], @@ -19,17 +20,17 @@ export const AllowServerCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const existing = await pluginData.state.allowedGuilds.find(args.guildId); if (existing) { - sendErrorMessage(pluginData, msg.channel, "Server is already allowed!"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "Server is already allowed!"); return; } if (!isSnowflake(args.guildId)) { - sendErrorMessage(pluginData, msg.channel, "Invalid server ID!"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "Invalid server ID!"); return; } if (args.userId && !isSnowflake(args.userId)) { - sendErrorMessage(pluginData, msg.channel, "Invalid user ID!"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "Invalid user ID!"); return; } @@ -40,6 +41,6 @@ export const AllowServerCmd = botControlCmd({ await pluginData.state.apiPermissionAssignments.addUser(args.guildId, args.userId, [ApiPermissions.EditConfig]); } - sendSuccessMessage(pluginData, msg.channel, "Server is now allowed to use Zeppelin!"); + sendSuccessMessage(pluginData, msg.channel as TextChannel, "Server is now allowed to use Zeppelin!"); }, }); diff --git a/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts b/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts index fa882b8e..1a9cf64a 100644 --- a/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts @@ -1,7 +1,8 @@ -import { botControlCmd } from "../types"; -import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { Snowflake, TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { noop } from "../../../utils"; +import { botControlCmd } from "../types"; export const DisallowServerCmd = botControlCmd({ trigger: ["disallow_server", "disallowserver", "remove_server", "removeserver"], @@ -17,12 +18,15 @@ export const DisallowServerCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const existing = await pluginData.state.allowedGuilds.find(args.guildId); if (!existing) { - sendErrorMessage(pluginData, msg.channel, "That server is not allowed in the first place!"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "That server is not allowed in the first place!"); return; } await pluginData.state.allowedGuilds.remove(args.guildId); - await pluginData.client.leaveGuild(args.guildId).catch(noop); - sendSuccessMessage(pluginData, msg.channel, "Server removed!"); + await pluginData.client.guilds.cache + .get(args.guildId as Snowflake) + ?.leave() + .catch(noop); + sendSuccessMessage(pluginData, msg.channel as TextChannel, "Server removed!"); }, }); diff --git a/backend/src/plugins/BotControl/commands/EligibleCmd.ts b/backend/src/plugins/BotControl/commands/EligibleCmd.ts index 922b423b..522cf3a0 100644 --- a/backend/src/plugins/BotControl/commands/EligibleCmd.ts +++ b/backend/src/plugins/BotControl/commands/EligibleCmd.ts @@ -1,7 +1,8 @@ -import { botControlCmd } from "../types"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { resolveInvite, verboseUserMention } from "../../../utils"; +import { botControlCmd } from "../types"; const REQUIRED_MEMBER_COUNT = 5000; @@ -18,7 +19,7 @@ export const EligibleCmd = botControlCmd({ if ((await pluginData.state.apiPermissionAssignments.getByUserId(args.user.id)).length) { sendSuccessMessage( pluginData, - msg.channel, + msg.channel as TextChannel, `${verboseUserMention(args.user)} is an existing bot operator. They are eligible!`, ); return; @@ -26,17 +27,17 @@ export const EligibleCmd = botControlCmd({ const invite = await resolveInvite(pluginData.client, args.inviteCode, true); if (!invite || !invite.guild) { - sendErrorMessage(pluginData, msg.channel, "Could not resolve server from invite"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "Could not resolve server from invite"); return; } if (invite.guild.features.includes("PARTNERED")) { - sendSuccessMessage(pluginData, msg.channel, `Server is partnered. It is eligible!`); + sendSuccessMessage(pluginData, msg.channel as TextChannel, `Server is partnered. It is eligible!`); return; } if (invite.guild.features.includes("VERIFIED")) { - sendSuccessMessage(pluginData, msg.channel, `Server is verified. It is eligible!`); + sendSuccessMessage(pluginData, msg.channel as TextChannel, `Server is verified. It is eligible!`); return; } @@ -44,7 +45,7 @@ export const EligibleCmd = botControlCmd({ if (memberCount >= REQUIRED_MEMBER_COUNT) { sendSuccessMessage( pluginData, - msg.channel, + msg.channel as TextChannel, `Server has ${memberCount} members, which is equal or higher than the required ${REQUIRED_MEMBER_COUNT}. It is eligible!`, ); return; @@ -52,7 +53,7 @@ export const EligibleCmd = botControlCmd({ sendErrorMessage( pluginData, - msg.channel, + msg.channel as TextChannel, `Server **${invite.guild.name}** (\`${invite.guild.id}\`) is not eligible`, ); }, diff --git a/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts b/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts index 51b47d1d..b502c08f 100644 --- a/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts @@ -1,6 +1,7 @@ -import { botControlCmd } from "../types"; -import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { Snowflake, TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { botControlCmd } from "../types"; export const LeaveServerCmd = botControlCmd({ trigger: ["leave_server", "leave_guild"], @@ -14,21 +15,21 @@ export const LeaveServerCmd = botControlCmd({ }, async run({ pluginData, message: msg, args }) { - if (!pluginData.client.guilds.has(args.guildId)) { - sendErrorMessage(pluginData, msg.channel, "I am not in that guild"); + if (!pluginData.client.guilds.cache.has(args.guildId as Snowflake)) { + sendErrorMessage(pluginData, msg.channel as TextChannel, "I am not in that guild"); return; } - const guildToLeave = pluginData.client.guilds.get(args.guildId)!; + const guildToLeave = await pluginData.client.guilds.fetch(args.guildId as Snowflake)!; const guildName = guildToLeave.name; try { - await pluginData.client.leaveGuild(args.guildId); + await pluginData.client.guilds.cache.get(args.guildId as Snowflake)?.leave(); } catch (e) { - sendErrorMessage(pluginData, msg.channel, `Failed to leave guild: ${e.message}`); + sendErrorMessage(pluginData, msg.channel as TextChannel, `Failed to leave guild: ${e.message}`); return; } - sendSuccessMessage(pluginData, msg.channel, `Left guild **${guildName}**`); + sendSuccessMessage(pluginData, msg.channel as TextChannel, `Left guild **${guildName}**`); }, }); diff --git a/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts b/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts index aa047547..4a8a609c 100644 --- a/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts +++ b/backend/src/plugins/BotControl/commands/ListDashboardPermsCmd.ts @@ -1,9 +1,10 @@ -import { botControlCmd } from "../types"; -import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { createChunkedMessage, resolveUser } from "../../../utils"; import { AllowedGuild } from "../../../data/entities/AllowedGuild"; import { ApiPermissionAssignment } from "../../../data/entities/ApiPermissionAssignment"; +import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { resolveUser } from "../../../utils"; +import { botControlCmd } from "../types"; export const ListDashboardPermsCmd = botControlCmd({ trigger: ["list_dashboard_permissions", "list_dashboard_perms", "list_dash_permissionss", "list_dash_perms"], @@ -19,7 +20,7 @@ export const ListDashboardPermsCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { if (!args.user && !args.guildId) { - sendErrorMessage(pluginData, msg.channel, "Must specify at least guildId, user, or both."); + sendErrorMessage(pluginData, msg.channel as TextChannel, "Must specify at least guildId, user, or both."); return; } @@ -27,7 +28,7 @@ export const ListDashboardPermsCmd = botControlCmd({ if (args.guildId) { guild = await pluginData.state.allowedGuilds.find(args.guildId); if (!guild) { - sendErrorMessage(pluginData, msg.channel, "Server is not using Zeppelin"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "Server is not using Zeppelin"); return; } } @@ -36,7 +37,7 @@ export const ListDashboardPermsCmd = botControlCmd({ if (args.user) { existingUserAssignment = await pluginData.state.apiPermissionAssignments.getByUserId(args.user.id); if (existingUserAssignment.length === 0) { - sendErrorMessage(pluginData, msg.channel, "The user has no assigned permissions."); + sendErrorMessage(pluginData, msg.channel as TextChannel, "The user has no assigned permissions."); return; } } @@ -45,7 +46,7 @@ export const ListDashboardPermsCmd = botControlCmd({ // If we have user, always display which guilds they have permissions in (or only specified guild permissions) if (args.user) { - const userInfo = `**${args.user.username}#${args.user.discriminator}** (\`${args.user.id}\`)`; + const userInfo = `**${args.user.tag}** (\`${args.user.id}\`)`; for (const assignment of existingUserAssignment!) { if (guild != null && assignment.guild_id !== args.guildId) continue; @@ -59,7 +60,7 @@ export const ListDashboardPermsCmd = botControlCmd({ if (finalMessage === "") { sendErrorMessage( pluginData, - msg.channel, + msg.channel as TextChannel, `The user ${userInfo} has no assigned permissions on the specified server.`, ); return; @@ -70,19 +71,21 @@ export const ListDashboardPermsCmd = botControlCmd({ const existingGuildAssignment = await pluginData.state.apiPermissionAssignments.getByGuildId(guild.id); if (existingGuildAssignment.length === 0) { - sendErrorMessage(pluginData, msg.channel, `The server ${guildInfo} has no assigned permissions.`); + sendErrorMessage( + pluginData, + msg.channel as TextChannel, + `The server ${guildInfo} has no assigned permissions.`, + ); return; } finalMessage += `The server ${guildInfo} has the following assigned permissions:\n`; // Double \n for consistency with AddDashboardUserCmd for (const assignment of existingGuildAssignment) { const user = await resolveUser(pluginData.client, assignment.target_id); - finalMessage += `\n**${user.username}#${user.discriminator}**, \`${ - assignment.target_id - }\`: ${assignment.permissions.join(", ")}`; + finalMessage += `\n**${user.tag}**, \`${assignment.target_id}\`: ${assignment.permissions.join(", ")}`; } } - await sendSuccessMessage(pluginData, msg.channel, finalMessage.trim(), {}); + await sendSuccessMessage(pluginData, msg.channel as TextChannel, finalMessage.trim(), {}); }, }); diff --git a/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts b/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts index f4f89838..52c3083f 100644 --- a/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts +++ b/backend/src/plugins/BotControl/commands/ListDashboardUsersCmd.ts @@ -1,9 +1,8 @@ -import { botControlCmd } from "../types"; -import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { ApiPermissions } from "@shared/apiPermissions"; -import { resolveUser, UnknownUser } from "../../../utils"; -import { User } from "eris"; +import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { resolveUser } from "../../../utils"; +import { botControlCmd } from "../types"; export const ListDashboardUsersCmd = botControlCmd({ trigger: ["list_dashboard_users"], @@ -19,19 +18,17 @@ export const ListDashboardUsersCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const guild = await pluginData.state.allowedGuilds.find(args.guildId); if (!guild) { - sendErrorMessage(pluginData, msg.channel, "Server is not using Zeppelin"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "Server is not using Zeppelin"); return; } const dashboardUsers = await pluginData.state.apiPermissionAssignments.getByGuildId(guild.id); const users = await Promise.all(dashboardUsers.map(perm => resolveUser(pluginData.client, perm.target_id))); - const userNameList = users.map( - user => `<@!${user.id}> (**${user.username}#${user.discriminator}**, \`${user.id}\`)`, - ); + const userNameList = users.map(user => `<@!${user.id}> (**${user.tag}**, \`${user.id}\`)`); sendSuccessMessage( pluginData, - msg.channel, + msg.channel as TextChannel, `The following users have dashboard access for **${guild.name}**:\n\n${userNameList}`, {}, ); diff --git a/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts b/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts index 6abc0c5f..1b3629ce 100644 --- a/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts +++ b/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts @@ -1,7 +1,7 @@ -import { botControlCmd } from "../types"; +import { TextChannel } from "discord.js"; import { isOwnerPreFilter } from "../../../pluginUtils"; import { getActiveReload, setActiveReload } from "../activeReload"; -import { TextChannel } from "eris"; +import { botControlCmd } from "../types"; export const ReloadGlobalPluginsCmd = botControlCmd({ trigger: "bot_reload_global_plugins", @@ -14,7 +14,7 @@ export const ReloadGlobalPluginsCmd = botControlCmd({ if (getActiveReload()) return; setActiveReload((message.channel as TextChannel).guild?.id, message.channel.id); - await message.channel.createMessage("Reloading global plugins..."); + await message.channel.send("Reloading global plugins..."); pluginData.getKnubInstance().reloadGlobalContext(); }, diff --git a/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts b/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts index 7c725e11..88193fba 100644 --- a/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts @@ -1,6 +1,7 @@ -import { botControlCmd } from "../types"; -import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { Snowflake, TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { botControlCmd } from "../types"; export const ReloadServerCmd = botControlCmd({ trigger: ["reload_server", "reload_guild"], @@ -10,23 +11,23 @@ export const ReloadServerCmd = botControlCmd({ }, signature: { - guildId: ct.string(), + guildId: ct.anyId(), }, async run({ pluginData, message: msg, args }) { - if (!pluginData.client.guilds.has(args.guildId)) { - sendErrorMessage(pluginData, msg.channel, "I am not in that guild"); + if (!pluginData.client.guilds.cache.has(args.guildId as Snowflake)) { + sendErrorMessage(pluginData, msg.channel as TextChannel, "I am not in that guild"); return; } try { await pluginData.getKnubInstance().reloadGuild(args.guildId); } catch (e) { - sendErrorMessage(pluginData, msg.channel, `Failed to reload guild: ${e.message}`); + sendErrorMessage(pluginData, msg.channel as TextChannel, `Failed to reload guild: ${e.message}`); return; } - const guild = pluginData.client.guilds.get(args.guildId); - sendSuccessMessage(pluginData, msg.channel, `Reloaded guild **${guild?.name || "???"}**`); + const guild = await pluginData.client.guilds.fetch(args.guildId as Snowflake); + sendSuccessMessage(pluginData, msg.channel as TextChannel, `Reloaded guild **${guild?.name || "???"}**`); }, }); diff --git a/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts b/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts index 078c189c..3dfafb42 100644 --- a/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts +++ b/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts @@ -1,7 +1,7 @@ -import { botControlCmd } from "../types"; -import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { ApiPermissions } from "@shared/apiPermissions"; +import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { botControlCmd } from "../types"; export const RemoveDashboardUserCmd = botControlCmd({ trigger: ["remove_dashboard_user"], @@ -18,7 +18,7 @@ export const RemoveDashboardUserCmd = botControlCmd({ async run({ pluginData, message: msg, args }) { const guild = await pluginData.state.allowedGuilds.find(args.guildId); if (!guild) { - sendErrorMessage(pluginData, msg.channel, "Server is not using Zeppelin"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "Server is not using Zeppelin"); return; } @@ -34,12 +34,10 @@ export const RemoveDashboardUserCmd = botControlCmd({ await pluginData.state.apiPermissionAssignments.removeUser(args.guildId, user.id); } - const userNameList = args.users.map( - user => `<@!${user.id}> (**${user.username}#${user.discriminator}**, \`${user.id}\`)`, - ); + const userNameList = args.users.map(user => `<@!${user.id}> (**${user.tag}**, \`${user.id}\`)`); sendSuccessMessage( pluginData, - msg.channel, + msg.channel as TextChannel, `The following users were removed from the dashboard for **${guild.name}**:\n\n${userNameList}`, ); }, diff --git a/backend/src/plugins/BotControl/commands/ServersCmd.ts b/backend/src/plugins/BotControl/commands/ServersCmd.ts index 7d4be31c..d226e14c 100644 --- a/backend/src/plugins/BotControl/commands/ServersCmd.ts +++ b/backend/src/plugins/BotControl/commands/ServersCmd.ts @@ -1,8 +1,9 @@ -import { botControlCmd } from "../types"; -import { isOwnerPreFilter } from "../../../pluginUtils"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { TextChannel } from "discord.js"; import escapeStringRegexp from "escape-string-regexp"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { isOwnerPreFilter } from "../../../pluginUtils"; import { createChunkedMessage, getUser, sorter } from "../../../utils"; +import { botControlCmd } from "../types"; export const ServersCmd = botControlCmd({ trigger: ["servers", "guilds"], @@ -14,16 +15,16 @@ export const ServersCmd = botControlCmd({ signature: { search: ct.string({ catchAll: true, required: false }), - all: ct.switchOption({ shortcut: "a" }), - initialized: ct.switchOption({ shortcut: "i" }), - uninitialized: ct.switchOption({ shortcut: "u" }), + all: ct.switchOption({ def: false, shortcut: "a" }), + initialized: ct.switchOption({ def: false, shortcut: "i" }), + uninitialized: ct.switchOption({ def: false, shortcut: "u" }), }, async run({ pluginData, message: msg, args }) { const showList = Boolean(args.all || args.initialized || args.uninitialized || args.search); const search = args.search ? new RegExp([...args.search].map(s => escapeStringRegexp(s)).join(".*"), "i") : null; - const joinedGuilds = Array.from(pluginData.client.guilds.values()); + const joinedGuilds = Array.from(pluginData.client.guilds.cache.values()); const loadedGuilds = pluginData.getKnubInstance().getLoadedGuilds(); const loadedGuildsMap = loadedGuilds.reduce((map, guildData) => map.set(guildData.guildId, guildData), new Map()); @@ -47,19 +48,19 @@ export const ServersCmd = botControlCmd({ const longestId = filteredGuilds.reduce((longest, guild) => Math.max(longest, guild.id.length), 0); const lines = filteredGuilds.map(g => { const paddedId = g.id.padEnd(longestId, " "); - const owner = getUser(pluginData.client, g.ownerID); - return `\`${paddedId}\` **${g.name}** (${g.memberCount} members) (owner **${owner.username}#${owner.discriminator}** \`${owner.id}\`)`; + const owner = getUser(pluginData.client, g.ownerId); + return `\`${paddedId}\` **${g.name}** (${g.memberCount} members) (owner **${owner.tag}** \`${owner.id}\`)`; }); - createChunkedMessage(msg.channel, lines.join("\n")); + createChunkedMessage(msg.channel as TextChannel, lines.join("\n")); } else { - msg.channel.createMessage("No servers matched the filters"); + msg.channel.send("No servers matched the filters"); } } else { const total = joinedGuilds.length; const initialized = joinedGuilds.filter(g => loadedGuildsMap.has(g.id)).length; const unInitialized = total - initialized; - msg.channel.createMessage( + msg.channel.send( `I am on **${total} total servers**, of which **${initialized} are initialized** and **${unInitialized} are not initialized**`, ); } diff --git a/backend/src/plugins/BotControl/types.ts b/backend/src/plugins/BotControl/types.ts index d84a526d..1acac3a4 100644 --- a/backend/src/plugins/BotControl/types.ts +++ b/backend/src/plugins/BotControl/types.ts @@ -1,10 +1,10 @@ import * as t from "io-ts"; -import { tNullable } from "../../utils"; import { BasePluginType, typedGlobalCommand, typedGlobalEventListener } from "knub"; -import { GuildArchives } from "../../data/GuildArchives"; import { AllowedGuilds } from "../../data/AllowedGuilds"; import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments"; import { Configs } from "../../data/Configs"; +import { GuildArchives } from "../../data/GuildArchives"; +import { tNullable } from "../../utils"; export const ConfigSchema = t.type({ can_use: t.boolean, diff --git a/backend/src/plugins/Cases/CasesPlugin.ts b/backend/src/plugins/Cases/CasesPlugin.ts index 2bc0d861..6f870fc6 100644 --- a/backend/src/plugins/Cases/CasesPlugin.ts +++ b/backend/src/plugins/Cases/CasesPlugin.ts @@ -1,21 +1,21 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { CaseArgs, CaseNoteArgs, CasesPluginType, ConfigSchema } from "./types"; -import { createCase } from "./functions/createCase"; -import { GuildLogs } from "../../data/GuildLogs"; +import { CaseTypes } from "../../data/CaseTypes"; +import { Case } from "../../data/entities/Case"; import { GuildArchives } from "../../data/GuildArchives"; import { GuildCases } from "../../data/GuildCases"; -import { createCaseNote } from "./functions/createCaseNote"; -import { Case } from "../../data/entities/Case"; -import { postCaseToCaseLogChannel } from "./functions/postToCaseLogChannel"; -import { CaseTypes } from "../../data/CaseTypes"; -import { getCaseTypeAmountForUserId } from "./functions/getCaseTypeAmountForUserId"; -import { getCaseEmbed } from "./functions/getCaseEmbed"; -import { trimPluginDescription } from "../../utils"; -import { getCaseSummary } from "./functions/getCaseSummary"; -import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; +import { GuildLogs } from "../../data/GuildLogs"; import { mapToPublicFn } from "../../pluginUtils"; -import { getTotalCasesByMod } from "./functions/getTotalCasesByMod"; +import { trimPluginDescription } from "../../utils"; +import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import { createCase } from "./functions/createCase"; +import { createCaseNote } from "./functions/createCaseNote"; +import { getCaseEmbed } from "./functions/getCaseEmbed"; +import { getCaseSummary } from "./functions/getCaseSummary"; +import { getCaseTypeAmountForUserId } from "./functions/getCaseTypeAmountForUserId"; import { getRecentCasesByMod } from "./functions/getRecentCasesByMod"; +import { getTotalCasesByMod } from "./functions/getTotalCasesByMod"; +import { postCaseToCaseLogChannel } from "./functions/postToCaseLogChannel"; +import { CaseArgs, CaseNoteArgs, CasesPluginType, ConfigSchema } from "./types"; const defaultOptions = { config: { diff --git a/backend/src/plugins/Cases/functions/createCase.ts b/backend/src/plugins/Cases/functions/createCase.ts index fd3caa04..9df38f26 100644 --- a/backend/src/plugins/Cases/functions/createCase.ts +++ b/backend/src/plugins/Cases/functions/createCase.ts @@ -1,21 +1,21 @@ -import { CaseArgs, CasesPluginType } from "../types"; -import { resolveUser } from "../../../utils"; import { GuildPluginData } from "knub"; +import { logger } from "../../../logger"; +import { resolveUser } from "../../../utils"; +import { CaseArgs, CasesPluginType } from "../types"; import { createCaseNote } from "./createCaseNote"; import { postCaseToCaseLogChannel } from "./postToCaseLogChannel"; -import { logger } from "../../../logger"; export async function createCase(pluginData: GuildPluginData, args: CaseArgs) { const user = await resolveUser(pluginData.client, args.userId); - const userName = `${user.username}#${user.discriminator}`; + const userName = user.tag; const mod = await resolveUser(pluginData.client, args.modId); - const modName = `${mod.username}#${mod.discriminator}`; + const modName = mod.tag; let ppName: string | null = null; if (args.ppId) { const pp = await resolveUser(pluginData.client, args.ppId); - ppName = `${pp.username}#${pp.discriminator}`; + ppName = pp.tag; } if (args.auditLogId) { diff --git a/backend/src/plugins/Cases/functions/createCaseNote.ts b/backend/src/plugins/Cases/functions/createCaseNote.ts index cd3ae649..c03a5b3c 100644 --- a/backend/src/plugins/Cases/functions/createCaseNote.ts +++ b/backend/src/plugins/Cases/functions/createCaseNote.ts @@ -1,9 +1,9 @@ -import { CaseNoteArgs, CasesPluginType } from "../types"; import { GuildPluginData } from "knub"; import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; -import { resolveCaseId } from "./resolveCaseId"; -import { postCaseToCaseLogChannel } from "./postToCaseLogChannel"; import { resolveUser, UnknownUser } from "../../../utils"; +import { CaseNoteArgs, CasesPluginType } from "../types"; +import { postCaseToCaseLogChannel } from "./postToCaseLogChannel"; +import { resolveCaseId } from "./resolveCaseId"; export async function createCaseNote(pluginData: GuildPluginData, args: CaseNoteArgs): Promise { const theCase = await pluginData.state.cases.find(resolveCaseId(args.caseId)); @@ -16,7 +16,7 @@ export async function createCaseNote(pluginData: GuildPluginData, caseType: CaseTypes) { return pluginData.config.get().case_colors?.[CaseTypeToName[caseType]] ?? caseColors[caseType]; diff --git a/backend/src/plugins/Cases/functions/getCaseEmbed.ts b/backend/src/plugins/Cases/functions/getCaseEmbed.ts index ec92e059..93ee02ef 100644 --- a/backend/src/plugins/Cases/functions/getCaseEmbed.ts +++ b/backend/src/plugins/Cases/functions/getCaseEmbed.ts @@ -1,20 +1,20 @@ -import { Case } from "../../../data/entities/Case"; -import { AdvancedMessageContent, MessageContent } from "eris"; +import { MessageEditOptions, MessageOptions } from "discord.js"; +import { GuildPluginData } from "knub"; import moment from "moment-timezone"; import { CaseTypes } from "../../../data/CaseTypes"; -import { GuildPluginData, helpers } from "knub"; -import { CasesPluginType } from "../types"; -import { resolveCaseId } from "./resolveCaseId"; -import { chunkLines, chunkMessageLines, emptyEmbedValue, messageLink } from "../../../utils"; -import { getCaseColor } from "./getCaseColor"; +import { Case } from "../../../data/entities/Case"; +import { chunkMessageLines, emptyEmbedValue, messageLink } from "../../../utils"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { CasesPluginType } from "../types"; +import { getCaseColor } from "./getCaseColor"; +import { resolveCaseId } from "./resolveCaseId"; export async function getCaseEmbed( pluginData: GuildPluginData, caseOrCaseId: Case | number, requestMemberId?: string, noOriginalCaseLink?: boolean, -): Promise { +): Promise { const theCase = await pluginData.state.cases.with("notes").find(resolveCaseId(caseOrCaseId)); if (!theCase) { throw new Error("Unknown case"); @@ -108,5 +108,5 @@ export async function getCaseEmbed( }); } - return { embed }; + return { embeds: [embed] }; } diff --git a/backend/src/plugins/Cases/functions/getCaseIcon.ts b/backend/src/plugins/Cases/functions/getCaseIcon.ts index eb8fc3d5..6748a7ef 100644 --- a/backend/src/plugins/Cases/functions/getCaseIcon.ts +++ b/backend/src/plugins/Cases/functions/getCaseIcon.ts @@ -1,7 +1,7 @@ import { GuildPluginData } from "knub"; -import { CasesPluginType } from "../types"; import { CaseTypes, CaseTypeToName } from "../../../data/CaseTypes"; import { caseIcons } from "../caseIcons"; +import { CasesPluginType } from "../types"; export function getCaseIcon(pluginData: GuildPluginData, caseType: CaseTypes) { return pluginData.config.get().case_icons?.[CaseTypeToName[caseType]] ?? caseIcons[caseType]; diff --git a/backend/src/plugins/Cases/functions/getCaseSummary.ts b/backend/src/plugins/Cases/functions/getCaseSummary.ts index d948fdac..98841451 100644 --- a/backend/src/plugins/Cases/functions/getCaseSummary.ts +++ b/backend/src/plugins/Cases/functions/getCaseSummary.ts @@ -1,22 +1,12 @@ import { GuildPluginData } from "knub"; -import { CasesPluginType } from "../types"; -import { - convertDelayStringToMS, - DAYS, - DBDateFormat, - disableLinkPreviews, - emptyEmbedValue, - messageLink, -} from "../../../utils"; -import { CaseTypes, CaseTypeToName } from "../../../data/CaseTypes"; +import { splitMessageIntoChunks } from "knub/dist/helpers"; import moment from "moment-timezone"; import { Case } from "../../../data/entities/Case"; -import humanizeDuration from "humanize-duration"; -import { humanizeDurationShort } from "../../../humanizeDurationShort"; -import { caseAbbreviations } from "../caseAbbreviations"; -import { getCaseIcon } from "./getCaseIcon"; +import { convertDelayStringToMS, DAYS, DBDateFormat, disableLinkPreviews, messageLink } from "../../../utils"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; -import { splitIntoCleanChunks, splitMessageIntoChunks } from "knub/dist/helpers"; +import { caseAbbreviations } from "../caseAbbreviations"; +import { CasesPluginType } from "../types"; +import { getCaseIcon } from "./getCaseIcon"; const CASE_SUMMARY_REASON_MAX_LENGTH = 300; const INCLUDE_MORE_NOTES_THRESHOLD = 20; diff --git a/backend/src/plugins/Cases/functions/getCaseTypeAmountForUserId.ts b/backend/src/plugins/Cases/functions/getCaseTypeAmountForUserId.ts index 60752242..94f35d7f 100644 --- a/backend/src/plugins/Cases/functions/getCaseTypeAmountForUserId.ts +++ b/backend/src/plugins/Cases/functions/getCaseTypeAmountForUserId.ts @@ -1,6 +1,6 @@ import { GuildPluginData } from "knub"; -import { CasesPluginType } from "../types"; import { CaseTypes } from "../../../data/CaseTypes"; +import { CasesPluginType } from "../types"; export async function getCaseTypeAmountForUserId( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Cases/functions/getRecentCasesByMod.ts b/backend/src/plugins/Cases/functions/getRecentCasesByMod.ts index 89ec703c..b9988078 100644 --- a/backend/src/plugins/Cases/functions/getRecentCasesByMod.ts +++ b/backend/src/plugins/Cases/functions/getRecentCasesByMod.ts @@ -1,6 +1,6 @@ import { GuildPluginData } from "knub"; -import { CasesPluginType } from "../types"; import { Case } from "../../../data/entities/Case"; +import { CasesPluginType } from "../types"; export function getRecentCasesByMod( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Cases/functions/postToCaseLogChannel.ts b/backend/src/plugins/Cases/functions/postToCaseLogChannel.ts index 604293e1..7f3f04d5 100644 --- a/backend/src/plugins/Cases/functions/postToCaseLogChannel.ts +++ b/backend/src/plugins/Cases/functions/postToCaseLogChannel.ts @@ -1,29 +1,31 @@ +import { FileOptions, Message, MessageOptions, Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { CasesPluginType } from "../types"; -import { Message, MessageContent, MessageFile, TextChannel } from "eris"; -import { isDiscordRESTError } from "../../../utils"; -import { LogType } from "../../../data/LogType"; import { Case } from "../../../data/entities/Case"; +import { LogType } from "../../../data/LogType"; +import { isDiscordAPIError } from "../../../utils"; +import { CasesPluginType } from "../types"; import { getCaseEmbed } from "./getCaseEmbed"; import { resolveCaseId } from "./resolveCaseId"; -import { logger } from "../../../logger"; export async function postToCaseLogChannel( pluginData: GuildPluginData, - content: MessageContent, - file?: MessageFile, + content: MessageOptions, + file?: FileOptions[], ): Promise { const caseLogChannelId = pluginData.config.get().case_log_channel; if (!caseLogChannelId) return null; - const caseLogChannel = pluginData.guild.channels.get(caseLogChannelId); + const caseLogChannel = pluginData.guild.channels.cache.get(caseLogChannelId as Snowflake); if (!caseLogChannel || !(caseLogChannel instanceof TextChannel)) return null; let result; try { - result = await caseLogChannel.createMessage(content, file); + if (file != null) { + content.files = file; + } + result = await caseLogChannel.send({ ...content }); } catch (e) { - if (isDiscordRESTError(e) && (e.code === 50013 || e.code === 50001)) { + if (isDiscordAPIError(e) && (e.code === 50013 || e.code === 50001)) { pluginData.state.logs.log(LogType.BOT_ALERT, { body: `Missing permissions to post mod cases in <#${caseLogChannel.id}>`, }); @@ -50,7 +52,8 @@ export async function postCaseToCaseLogChannel( const [channelId, messageId] = theCase.log_message_id.split("-"); try { - await pluginData.client.editMessage(channelId, messageId, caseEmbed); + const channel = pluginData.guild.channels.resolve(channelId as Snowflake) as TextChannel; + await channel.messages.edit(messageId as Snowflake, caseEmbed); return null; } catch {} // tslint:disable-line:no-empty } diff --git a/backend/src/plugins/Cases/types.ts b/backend/src/plugins/Cases/types.ts index 94fe1920..9ec1a371 100644 --- a/backend/src/plugins/Cases/types.ts +++ b/backend/src/plugins/Cases/types.ts @@ -1,10 +1,10 @@ import * as t from "io-ts"; -import { tDelayString, tPartialDictionary, tNullable } from "../../utils"; +import { BasePluginType } from "knub"; import { CaseNameToType, CaseTypes } from "../../data/CaseTypes"; -import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub"; -import { GuildLogs } from "../../data/GuildLogs"; -import { GuildCases } from "../../data/GuildCases"; import { GuildArchives } from "../../data/GuildArchives"; +import { GuildCases } from "../../data/GuildCases"; +import { GuildLogs } from "../../data/GuildLogs"; +import { tDelayString, tNullable, tPartialDictionary } from "../../utils"; import { tColor } from "../../utils/tColor"; export const ConfigSchema = t.type({ diff --git a/backend/src/plugins/Censor/CensorPlugin.ts b/backend/src/plugins/Censor/CensorPlugin.ts index 4dc103ec..f8401f54 100644 --- a/backend/src/plugins/Censor/CensorPlugin.ts +++ b/backend/src/plugins/Censor/CensorPlugin.ts @@ -1,13 +1,13 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { PluginOptions } from "knub"; -import { ConfigSchema, CensorPluginType } from "./types"; import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners"; +import { trimPluginDescription } from "../../utils"; +import { LogsPlugin } from "../Logs/LogsPlugin"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import { CensorPluginType, ConfigSchema } from "./types"; import { onMessageCreate } from "./util/onMessageCreate"; import { onMessageUpdate } from "./util/onMessageUpdate"; -import { trimPluginDescription } from "../../utils"; -import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners"; -import { LogsPlugin } from "../Logs/LogsPlugin"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/Censor/types.ts b/backend/src/plugins/Censor/types.ts index 5cc64f5d..1363baef 100644 --- a/backend/src/plugins/Censor/types.ts +++ b/backend/src/plugins/Censor/types.ts @@ -1,10 +1,10 @@ import * as t from "io-ts"; import { BasePluginType } from "knub"; -import { tNullable } from "../../utils"; -import { TRegex } from "../../validatorUtils"; import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { RegExpRunner } from "../../RegExpRunner"; +import { tNullable } from "../../utils"; +import { TRegex } from "../../validatorUtils"; export const ConfigSchema = t.type({ filter_zalgo: t.boolean, diff --git a/backend/src/plugins/Censor/util/applyFiltersToMsg.ts b/backend/src/plugins/Censor/util/applyFiltersToMsg.ts index 4e9f3691..ffadcc67 100644 --- a/backend/src/plugins/Censor/util/applyFiltersToMsg.ts +++ b/backend/src/plugins/Censor/util/applyFiltersToMsg.ts @@ -1,14 +1,13 @@ -import { GuildPluginData } from "knub"; -import { CensorPluginType } from "../types"; -import { SavedMessage } from "../../../data/entities/SavedMessage"; -import { Embed, Invite } from "eris"; -import { ZalgoRegex } from "../../../data/Zalgo"; -import { getInviteCodesInString, getUrlsInString, resolveMember, resolveInvite, isGuildInvite } from "../../../utils"; -import cloneDeep from "lodash.clonedeep"; -import { censorMessage } from "./censorMessage"; +import { Invite, MessageEmbed } from "discord.js"; import escapeStringRegexp from "escape-string-regexp"; -import { logger } from "../../../logger"; +import { GuildPluginData } from "knub"; +import cloneDeep from "lodash.clonedeep"; +import { SavedMessage } from "../../../data/entities/SavedMessage"; +import { ZalgoRegex } from "../../../data/Zalgo"; import { allowTimeout } from "../../../RegExpRunner"; +import { getInviteCodesInString, getUrlsInString, isGuildInvite, resolveInvite, resolveMember } from "../../../utils"; +import { CensorPluginType } from "../types"; +import { censorMessage } from "./censorMessage"; export async function applyFiltersToMsg( pluginData: GuildPluginData, @@ -20,7 +19,7 @@ export async function applyFiltersToMsg( let messageContent = savedMessage.data.content || ""; if (savedMessage.data.attachments) messageContent += " " + JSON.stringify(savedMessage.data.attachments); if (savedMessage.data.embeds) { - const embeds = (savedMessage.data.embeds as Embed[]).map(e => cloneDeep(e)); + const embeds = (savedMessage.data.embeds as MessageEmbed[]).map(e => cloneDeep(e)); for (const embed of embeds) { if (embed.type === "video") { // Ignore video descriptions as they're not actually shown on the embed @@ -69,20 +68,20 @@ export async function applyFiltersToMsg( } if (isGuildInvite(invite)) { - if (inviteGuildWhitelist && !inviteGuildWhitelist.includes(invite.guild.id)) { + if (inviteGuildWhitelist && !inviteGuildWhitelist.includes(invite.guild!.id)) { censorMessage( pluginData, savedMessage, - `invite guild (**${invite.guild.name}** \`${invite.guild.id}\`) not found in whitelist`, + `invite guild (**${invite.guild!.name}** \`${invite.guild!.id}\`) not found in whitelist`, ); return true; } - if (inviteGuildBlacklist && inviteGuildBlacklist.includes(invite.guild.id)) { + if (inviteGuildBlacklist && inviteGuildBlacklist.includes(invite.guild!.id)) { censorMessage( pluginData, savedMessage, - `invite guild (**${invite.guild.name}** \`${invite.guild.id}\`) found in blacklist`, + `invite guild (**${invite.guild!.name}** \`${invite.guild!.id}\`) found in blacklist`, ); return true; } diff --git a/backend/src/plugins/Censor/util/censorMessage.ts b/backend/src/plugins/Censor/util/censorMessage.ts index 91302acf..509680e2 100644 --- a/backend/src/plugins/Censor/util/censorMessage.ts +++ b/backend/src/plugins/Censor/util/censorMessage.ts @@ -1,9 +1,11 @@ +import { Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { CensorPluginType } from "../types"; +import { deactivateMentions, disableCodeBlocks } from "knub/dist/helpers"; +import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { SavedMessage } from "../../../data/entities/SavedMessage"; import { LogType } from "../../../data/LogType"; -import { stripObjectToScalars, resolveUser } from "../../../utils"; -import { disableCodeBlocks, deactivateMentions } from "knub/dist/helpers"; +import { resolveUser } from "../../../utils"; +import { CensorPluginType } from "../types"; export async function censorMessage( pluginData: GuildPluginData, @@ -13,17 +15,18 @@ export async function censorMessage( pluginData.state.serverLogs.ignoreLog(LogType.MESSAGE_DELETE, savedMessage.id); try { - await pluginData.client.deleteMessage(savedMessage.channel_id, savedMessage.id, "Censored"); + const resolvedChannel = pluginData.guild.channels.resolve(savedMessage.channel_id as Snowflake) as TextChannel; + await resolvedChannel.messages.delete(savedMessage.id as Snowflake); } catch { return; } const user = await resolveUser(pluginData.client, savedMessage.user_id); - const channel = pluginData.guild.channels.get(savedMessage.channel_id); + const channel = pluginData.guild.channels.resolve(savedMessage.channel_id as Snowflake)!; pluginData.state.serverLogs.log(LogType.CENSOR, { - user: stripObjectToScalars(user), - channel: stripObjectToScalars(channel), + user: userToConfigAccessibleUser(user), + channel: channelToConfigAccessibleChannel(channel), reason, message: savedMessage, messageText: disableCodeBlocks(deactivateMentions(savedMessage.data.content)), diff --git a/backend/src/plugins/Censor/util/onMessageCreate.ts b/backend/src/plugins/Censor/util/onMessageCreate.ts index fb9cfaeb..482e9cff 100644 --- a/backend/src/plugins/Censor/util/onMessageCreate.ts +++ b/backend/src/plugins/Censor/util/onMessageCreate.ts @@ -1,8 +1,8 @@ import { GuildPluginData } from "knub"; -import { CensorPluginType } from "../types"; import { SavedMessage } from "../../../data/entities/SavedMessage"; -import { applyFiltersToMsg } from "./applyFiltersToMsg"; import { messageLock } from "../../../utils/lockNameHelpers"; +import { CensorPluginType } from "../types"; +import { applyFiltersToMsg } from "./applyFiltersToMsg"; export async function onMessageCreate(pluginData: GuildPluginData, savedMessage: SavedMessage) { if (savedMessage.is_bot) return; diff --git a/backend/src/plugins/Censor/util/onMessageUpdate.ts b/backend/src/plugins/Censor/util/onMessageUpdate.ts index 7afd4c17..d17bf6b0 100644 --- a/backend/src/plugins/Censor/util/onMessageUpdate.ts +++ b/backend/src/plugins/Censor/util/onMessageUpdate.ts @@ -1,8 +1,8 @@ import { GuildPluginData } from "knub"; -import { CensorPluginType } from "../types"; import { SavedMessage } from "../../../data/entities/SavedMessage"; -import { applyFiltersToMsg } from "./applyFiltersToMsg"; import { messageLock } from "../../../utils/lockNameHelpers"; +import { CensorPluginType } from "../types"; +import { applyFiltersToMsg } from "./applyFiltersToMsg"; export async function onMessageUpdate(pluginData: GuildPluginData, savedMessage: SavedMessage) { if (savedMessage.is_bot) return; diff --git a/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts b/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts index 249a5ef0..3349c242 100644 --- a/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts +++ b/backend/src/plugins/ChannelArchiver/ChannelArchiverPlugin.ts @@ -1,8 +1,8 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { ChannelArchiverPluginType } from "./types"; -import { ArchiveChannelCmd } from "./commands/ArchiveChannelCmd"; import * as t from "io-ts"; import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import { ArchiveChannelCmd } from "./commands/ArchiveChannelCmd"; +import { ChannelArchiverPluginType } from "./types"; export const ChannelArchiverPlugin = zeppelinGuildPlugin()({ name: "channel_archiver", diff --git a/backend/src/plugins/ChannelArchiver/commands/ArchiveChannelCmd.ts b/backend/src/plugins/ChannelArchiver/commands/ArchiveChannelCmd.ts index 373f2aa4..c1b3a871 100644 --- a/backend/src/plugins/ChannelArchiver/commands/ArchiveChannelCmd.ts +++ b/backend/src/plugins/ChannelArchiver/commands/ArchiveChannelCmd.ts @@ -1,10 +1,11 @@ -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { channelArchiverCmd } from "../types"; -import { isOwner, sendErrorMessage } from "../../../pluginUtils"; -import { confirm, SECONDS, noop } from "../../../utils"; +import { Collection, Message, Snowflake } from "discord.js"; import moment from "moment-timezone"; -import { rehostAttachment } from "../rehostAttachment"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { isOwner, sendErrorMessage } from "../../../pluginUtils"; +import { confirm, noop, SECONDS } from "../../../utils"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { rehostAttachment } from "../rehostAttachment"; +import { channelArchiverCmd } from "../types"; const MAX_ARCHIVED_MESSAGES = 5000; const MAX_MESSAGES_PER_FETCH = 100; @@ -31,12 +32,10 @@ export const ArchiveChannelCmd = channelArchiverCmd({ async run({ message: msg, args, pluginData }) { if (!args["attachment-channel"]) { - const confirmed = await confirm( - pluginData.client, - msg.channel, - msg.author.id, - "No `-attachment-channel` specified. Continue? Attachments will not be available in the log if their message is deleted.", - ); + const confirmed = await confirm(msg.channel, msg.author.id, { + content: + "No `-attachment-channel` specified. Continue? Attachments will not be available in the log if their message is deleted.", + }); if (!confirmed) { sendErrorMessage(pluginData, msg.channel, "Canceled"); return; @@ -51,7 +50,7 @@ export const ArchiveChannelCmd = channelArchiverCmd({ let previousId: string | undefined; const startTime = Date.now(); - const progressMsg = await msg.channel.createMessage("Creating archive..."); + const progressMsg = await msg.channel.send("Creating archive..."); const progressUpdateInterval = setInterval(() => { const secondsSinceStart = Math.round((Date.now() - startTime) / 1000); progressMsg @@ -61,16 +60,19 @@ export const ArchiveChannelCmd = channelArchiverCmd({ while (archivedMessages < maxMessagesToArchive) { const messagesToFetch = Math.min(MAX_MESSAGES_PER_FETCH, maxMessagesToArchive - archivedMessages); - const messages = await args.channel.getMessages(messagesToFetch, previousId); - if (messages.length === 0) break; + const messages = await args.channel.messages.fetch({ + limit: messagesToFetch, + before: previousId as Snowflake, + }); + if (messages.size === 0) break; - for (const message of messages) { - const ts = moment.utc(message.timestamp).format("YYYY-MM-DD HH:mm:ss"); + for (const message of messages.values()) { + const ts = moment.utc(message.createdTimestamp).format("YYYY-MM-DD HH:mm:ss"); let content = `[${ts}] [${message.author.id}] [${message.author.username}#${ message.author.discriminator }]: ${message.content || ""}`; - if (message.attachments.length) { + if (message.attachments.size) { if (args["attachment-channel"]) { const rehostedAttachmentUrl = await rehostAttachment(message.attachments[0], args["attachment-channel"]); content += `\n-- Attachment: ${rehostedAttachmentUrl}`; @@ -104,9 +106,14 @@ export const ArchiveChannelCmd = channelArchiverCmd({ result += `\n\n${archiveLines.join("\n")}\n`; progressMsg.delete().catch(noop); - msg.channel.createMessage("Archive created!", { - file: Buffer.from(result), - name: `archive-${args.channel.name}-${moment.utc().format("YYYY-MM-DD-HH-mm-ss")}.txt`, + msg.channel.send({ + content: "Archive created!", + files: [ + { + attachment: Buffer.from(result), + name: `archive-${args.channel.name}-${moment.utc().format("YYYY-MM-DD-HH-mm-ss")}.txt`, + }, + ], }); }, }); diff --git a/backend/src/plugins/ChannelArchiver/rehostAttachment.ts b/backend/src/plugins/ChannelArchiver/rehostAttachment.ts index c0bbd289..9f5c6423 100644 --- a/backend/src/plugins/ChannelArchiver/rehostAttachment.ts +++ b/backend/src/plugins/ChannelArchiver/rehostAttachment.ts @@ -1,11 +1,14 @@ -import { Attachment, TextChannel } from "eris"; -import { downloadFile } from "../../utils"; +import { MessageAttachment, MessageOptions, TextChannel, ThreadChannel } from "discord.js"; import fs from "fs"; +import { downloadFile } from "../../utils"; const fsp = fs.promises; const MAX_ATTACHMENT_REHOST_SIZE = 1024 * 1024 * 8; -export async function rehostAttachment(attachment: Attachment, targetChannel: TextChannel): Promise { +export async function rehostAttachment( + attachment: MessageAttachment, + targetChannel: TextChannel | ThreadChannel, +): Promise { if (attachment.size > MAX_ATTACHMENT_REHOST_SIZE) { return "Attachment too big to rehost"; } @@ -18,11 +21,12 @@ export async function rehostAttachment(attachment: Attachment, targetChannel: Te } try { - const rehostMessage = await targetChannel.createMessage(`Rehost of attachment ${attachment.id}`, { - name: attachment.filename, - file: await fsp.readFile(downloaded.path), - }); - return rehostMessage.attachments[0].url; + const content: MessageOptions = { + content: `Rehost of attachment ${attachment.id}`, + files: [{ name: attachment.name ? attachment.name : undefined, attachment: await fsp.readFile(downloaded.path) }], + }; + const rehostMessage = await targetChannel.send(content); + return rehostMessage.attachments.values()[0].url; } catch { return "Failed to rehost attachment"; } diff --git a/backend/src/plugins/CompanionChannels/CompanionChannelsPlugin.ts b/backend/src/plugins/CompanionChannels/CompanionChannelsPlugin.ts index 91d2358a..54196d78 100644 --- a/backend/src/plugins/CompanionChannels/CompanionChannelsPlugin.ts +++ b/backend/src/plugins/CompanionChannels/CompanionChannelsPlugin.ts @@ -1,11 +1,10 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { CompanionChannelsPluginType, ConfigSchema, TCompanionChannelOpts } from "./types"; -import { VoiceChannelJoinEvt } from "./events/VoiceChannelJoinEvt"; -import { VoiceChannelSwitchEvt } from "./events/VoiceChannelSwitchEvt"; -import { VoiceChannelLeaveEvt } from "./events/VoiceChannelLeaveEvt"; +import { CooldownManager } from "knub"; +import { GuildLogs } from "../../data/GuildLogs"; import { trimPluginDescription } from "../../utils"; import { LogsPlugin } from "../Logs/LogsPlugin"; -import { CooldownManager } from "knub"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import { VoiceStateUpdateEvt } from "./events/VoiceStateUpdateEvt"; +import { CompanionChannelsPluginType, ConfigSchema } from "./types"; const defaultOptions = { config: { @@ -29,9 +28,13 @@ export const CompanionChannelsPlugin = zeppelinGuildPlugin = { export async function getCompanionChannelOptsForVoiceChannelId( pluginData: GuildPluginData, userId: string, - voiceChannel: VoiceChannel, + voiceChannel: VoiceChannel | StageChannel, ): Promise { const config = await pluginData.config.getMatchingConfig({ userId, channelId: voiceChannel.id }); return Object.values(config.entries) .filter( opts => opts.voice_channel_ids.includes(voiceChannel.id) || - (voiceChannel.parentID && opts.voice_channel_ids.includes(voiceChannel.parentID)), + (voiceChannel.parentId && opts.voice_channel_ids.includes(voiceChannel.parentId)), ) .map(opts => Object.assign({}, defaultCompanionChannelOpts, opts)); } diff --git a/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts b/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts index 1dbdfe27..ea9c58ef 100644 --- a/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts +++ b/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts @@ -1,10 +1,10 @@ +import { Permissions, Snowflake, StageChannel, TextChannel, VoiceChannel } from "discord.js"; +import { GuildPluginData } from "knub"; +import { LogType } from "../../../data/LogType"; +import { isDiscordAPIError, MINUTES } from "../../../utils"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; import { CompanionChannelsPluginType, TCompanionChannelOpts } from "../types"; import { getCompanionChannelOptsForVoiceChannelId } from "./getCompanionChannelOptsForVoiceChannelId"; -import { GuildPluginData } from "knub"; -import { TextChannel, VoiceChannel } from "eris"; -import { isDiscordRESTError, MINUTES } from "../../../utils"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { LogType } from "../../../data/LogType"; const ERROR_COOLDOWN_KEY = "errorCooldown"; const ERROR_COOLDOWN = 5 * MINUTES; @@ -12,20 +12,8 @@ const ERROR_COOLDOWN = 5 * MINUTES; export async function handleCompanionPermissions( pluginData: GuildPluginData, userId: string, - voiceChannel: VoiceChannel, - oldChannel?: VoiceChannel, -); -export async function handleCompanionPermissions( - pluginData: GuildPluginData, - userId: string, - voiceChannel: null, - oldChannel: VoiceChannel, -); -export async function handleCompanionPermissions( - pluginData: GuildPluginData, - userId: string, - voiceChannel: VoiceChannel | null, - oldChannel?: VoiceChannel, + voiceChannel: VoiceChannel | StageChannel | null, + oldChannel?: VoiceChannel | StageChannel | null, ) { if (pluginData.state.errorCooldownManager.isOnCooldown(ERROR_COOLDOWN_KEY)) { return; @@ -63,24 +51,24 @@ export async function handleCompanionPermissions( try { for (const channelId of permsToDelete) { - const channel = pluginData.guild.channels.get(channelId); + const channel = pluginData.guild.channels.cache.get(channelId as Snowflake); if (!channel || !(channel instanceof TextChannel)) continue; - await channel.deletePermission(userId, `Companion Channel for ${oldChannel!.id} | User Left`); + pluginData.state.serverLogs.ignoreLog(LogType.CHANNEL_UPDATE, channelId, 3 * 1000); + await channel.permissionOverwrites + .resolve(userId as Snowflake) + ?.delete(`Companion Channel for ${oldChannel!.id} | User Left`); } for (const [channelId, permissions] of permsToSet) { - const channel = pluginData.guild.channels.get(channelId); + const channel = pluginData.guild.channels.cache.get(channelId as Snowflake); if (!channel || !(channel instanceof TextChannel)) continue; - await channel.editPermission( - userId, - permissions, - 0, - "member", - `Companion Channel for ${voiceChannel!.id} | User Joined`, - ); + pluginData.state.serverLogs.ignoreLog(LogType.CHANNEL_UPDATE, channelId, 3 * 1000); + await channel.permissionOverwrites.create(userId as Snowflake, new Permissions(BigInt(permissions)).serialize(), { + reason: `Companion Channel for ${voiceChannel!.id} | User Joined`, + }); } } catch (e) { - if (isDiscordRESTError(e) && e.code === 50001) { + if (isDiscordAPIError(e) && e.code === 50001) { const logs = pluginData.getPlugin(LogsPlugin); logs.log(LogType.BOT_ALERT, { body: `Missing permissions to handle companion channels. Pausing companion channels for 5 minutes or until the bot is reloaded on this server.`, diff --git a/backend/src/plugins/CompanionChannels/types.ts b/backend/src/plugins/CompanionChannels/types.ts index 1a91800d..070295af 100644 --- a/backend/src/plugins/CompanionChannels/types.ts +++ b/backend/src/plugins/CompanionChannels/types.ts @@ -1,8 +1,7 @@ import * as t from "io-ts"; -import { tNullable } from "../../utils"; import { BasePluginType, CooldownManager, typedGuildEventListener } from "knub"; import { GuildLogs } from "../../data/GuildLogs"; -import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { tNullable } from "../../utils"; // Permissions using these numbers: https://abal.moe/Eris/docs/reference (add all allowed/denied ones up) export const CompanionChannelOpts = t.type({ @@ -26,6 +25,7 @@ export interface CompanionChannelsPluginType extends BasePluginType { config: TConfigSchema; state: { errorCooldownManager: CooldownManager; + serverLogs: GuildLogs; }; } diff --git a/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts b/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts new file mode 100644 index 00000000..7aefe88f --- /dev/null +++ b/backend/src/plugins/ContextMenus/ContextMenuPlugin.ts @@ -0,0 +1,61 @@ +import { PluginOptions } from "knub"; +import { StrictValidationError } from "src/validatorUtils"; +import { ConfigPreprocessorFn } from "../../../../../Knub/dist/config/configTypes"; +import { GuildContextMenuLinks } from "../../data/GuildContextMenuLinks"; +import { LogsPlugin } from "../Logs/LogsPlugin"; +import { MutesPlugin } from "../Mutes/MutesPlugin"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import { availableTypes } from "./actions/availableActions"; +import { ContextClickedEvt } from "./events/ContextClickedEvt"; +import { ConfigSchema, ContextMenuPluginType } from "./types"; +import { loadAllCommands } from "./utils/loadAllCommands"; + +const defaultOptions: PluginOptions = { + config: { + context_actions: {}, + }, +}; + +const configPreprocessor: ConfigPreprocessorFn = options => { + if (options.config.context_actions) { + for (const [name, contextMenu] of Object.entries(options.config.context_actions)) { + if (Object.entries(contextMenu.action).length !== 1) { + throw new StrictValidationError([`Invalid value for context_actions/${name}: Must have exactly one action.`]); + } + + const actionName = Object.entries(contextMenu.action)[0][0]; + if (!availableTypes[actionName].includes(contextMenu.type)) { + throw new StrictValidationError([ + `Invalid value for context_actions/${name}/${actionName}: ${actionName} is not allowed on type ${contextMenu.type}.`, + ]); + } + } + } + + return options; +}; + +export const ContextMenuPlugin = zeppelinGuildPlugin()({ + name: "context_menu", + + configSchema: ConfigSchema, + defaultOptions, + configPreprocessor, + + // prettier-ignore + events: [ + ContextClickedEvt, + ], + + beforeLoad(pluginData) { + const { state, guild } = pluginData; + + state.contextMenuLinks = new GuildContextMenuLinks(guild.id); + }, + + afterLoad(pluginData) { + loadAllCommands(pluginData); + }, + + dependencies: [MutesPlugin, LogsPlugin], +}); diff --git a/backend/src/plugins/ContextMenus/actions/availableActions.ts b/backend/src/plugins/ContextMenus/actions/availableActions.ts new file mode 100644 index 00000000..24816790 --- /dev/null +++ b/backend/src/plugins/ContextMenus/actions/availableActions.ts @@ -0,0 +1,19 @@ +import * as t from "io-ts"; +import { ContextActionBlueprint } from "../helpers"; +import { CleanAction } from "./clean"; +import { MuteAction } from "./mute"; + +export const availableActions: Record> = { + mute: MuteAction, + clean: CleanAction, +}; + +export const AvailableActions = t.type({ + mute: MuteAction.configType, + clean: CleanAction.configType, +}); + +export const availableTypes: Record = { + mute: ["USER"], + clean: ["MESSAGE"], +}; diff --git a/backend/src/plugins/ContextMenus/actions/clean.ts b/backend/src/plugins/ContextMenus/actions/clean.ts new file mode 100644 index 00000000..8184d480 --- /dev/null +++ b/backend/src/plugins/ContextMenus/actions/clean.ts @@ -0,0 +1,64 @@ +import { TextChannel } from "discord.js"; +import * as t from "io-ts"; +import { canActOn } from "src/pluginUtils"; +import { LogType } from "../../../data/LogType"; +import { UtilityPlugin } from "../../../plugins/Utility/UtilityPlugin"; +import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; +import { tNullable } from "../../../utils"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { contextMenuAction } from "../helpers"; + +export const CleanAction = contextMenuAction({ + configType: t.type({ + amount: tNullable(t.number), + targetUserOnly: tNullable(t.boolean), + "delete-pins": tNullable(t.boolean), + }), + + defaultConfig: { + amount: 10, + targetUserOnly: false, + "delete-pins": false, + }, + + async apply({ pluginData, actionConfig, actionName, interaction }) { + interaction.deferReply({ ephemeral: true }); + const targetMessage = interaction.channel + ? await interaction.channel.messages.fetch(interaction.targetId) + : await (pluginData.guild.channels.resolve(interaction.channelId) as TextChannel).messages.fetch( + interaction.targetId, + ); + + const amount = actionConfig.amount ?? 10; + const targetUserOnly = actionConfig.targetUserOnly ?? false; + const deletePins = actionConfig["delete-pins"] ?? false; + + const user = targetUserOnly ? targetMessage.author.id : undefined; + const targetMember = await pluginData.guild.members.fetch(targetMessage.author.id); + const executingMember = await pluginData.guild.members.fetch(interaction.user.id); + const utility = pluginData.getPlugin(UtilityPlugin); + + if (targetUserOnly && !canActOn(pluginData, executingMember, targetMember)) { + interaction.followUp({ ephemeral: true, content: "Cannot clean users messages: insufficient permissions" }); + return; + } + + try { + interaction.followUp(`Cleaning... Amount: ${amount}, User Only: ${targetUserOnly}, Pins: ${deletePins}`); + utility.clean( + { count: amount, user, channel: targetMessage.channel.id, "delete-pins": deletePins }, + targetMessage, + ); + } catch (e) { + interaction.followUp({ ephemeral: true, content: "Plugin error, please check your BOT_ALERTs" }); + + if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) { + pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, { + body: `Failed to clean in <#${interaction.channelId}> in ContextMenu action \`${actionName}\``, + }); + } else { + throw e; + } + } + }, +}); diff --git a/backend/src/plugins/ContextMenus/actions/mute.ts b/backend/src/plugins/ContextMenus/actions/mute.ts new file mode 100644 index 00000000..00a9c131 --- /dev/null +++ b/backend/src/plugins/ContextMenus/actions/mute.ts @@ -0,0 +1,83 @@ +import humanizeDuration from "humanize-duration"; +import * as t from "io-ts"; +import { canActOn } from "src/pluginUtils"; +import { LogType } from "../../../data/LogType"; +import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; +import { convertDelayStringToMS, tDelayString, tNullable } from "../../../utils"; +import { CaseArgs } from "../../Cases/types"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { MutesPlugin } from "../../Mutes/MutesPlugin"; +import { contextMenuAction } from "../helpers"; +import { resolveActionContactMethods } from "../utils/resolveActionContactMethods"; + +export const MuteAction = contextMenuAction({ + configType: t.type({ + reason: tNullable(t.string), + duration: tNullable(tDelayString), + notify: tNullable(t.string), + notifyChannel: tNullable(t.string), + remove_roles_on_mute: tNullable(t.union([t.boolean, t.array(t.string)])), + restore_roles_on_mute: tNullable(t.union([t.boolean, t.array(t.string)])), + postInCaseLog: tNullable(t.boolean), + hide_case: tNullable(t.boolean), + }), + + defaultConfig: { + notify: null, // Use defaults from ModActions + hide_case: false, + }, + + async apply({ pluginData, actionConfig, actionName, interaction }) { + const duration = actionConfig.duration ? convertDelayStringToMS(actionConfig.duration)! : undefined; + const reason = actionConfig.reason || "Context Menu Action"; + const contactMethods = actionConfig.notify ? resolveActionContactMethods(pluginData, actionConfig) : undefined; + const rolesToRemove = actionConfig.remove_roles_on_mute; + const rolesToRestore = actionConfig.restore_roles_on_mute; + + const caseArgs: Partial = { + modId: pluginData.client.user!.id, + automatic: true, + postInCaseLogOverride: actionConfig.postInCaseLog ?? undefined, + hide: Boolean(actionConfig.hide_case), + }; + + interaction.deferReply({ ephemeral: true }); + const mutes = pluginData.getPlugin(MutesPlugin); + const userId = interaction.targetId; + const targetMember = await pluginData.guild.members.fetch(interaction.targetId); + const executingMember = await pluginData.guild.members.fetch(interaction.user.id); + + if (!canActOn(pluginData, executingMember, targetMember)) { + interaction.followUp({ ephemeral: true, content: "Cannot mute: insufficient permissions" }); + return; + } + + try { + const result = await mutes.muteUser( + userId, + duration, + reason, + { contactMethods, caseArgs, isAutomodAction: true }, + rolesToRemove, + rolesToRestore, + ); + + const muteMessage = `Muted **${result.case.user_name}** ${ + duration ? `for ${humanizeDuration(duration)}` : "indefinitely" + } (Case #${result.case.case_number}) (user notified via ${result.notifyResult.method ?? + "dm"})\nPlease update the new case with the \`update\` command`; + + interaction.followUp({ ephemeral: true, content: muteMessage }); + } catch (e) { + interaction.followUp({ ephemeral: true, content: "Plugin error, please check your BOT_ALERTs" }); + + if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) { + pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, { + body: `Failed to mute <@!${userId}> in ContextMenu action \`${actionName}\` because a mute role has not been specified in server config`, + }); + } else { + throw e; + } + } + }, +}); diff --git a/backend/src/plugins/ContextMenus/events/ContextClickedEvt.ts b/backend/src/plugins/ContextMenus/events/ContextClickedEvt.ts new file mode 100644 index 00000000..62675e41 --- /dev/null +++ b/backend/src/plugins/ContextMenus/events/ContextClickedEvt.ts @@ -0,0 +1,13 @@ +import { ContextMenuInteraction } from "discord.js"; +import { contextMenuEvt } from "../types"; +import { routeContextAction } from "../utils/contextRouter"; + +export const ContextClickedEvt = contextMenuEvt({ + event: "interactionCreate", + + async listener(meta) { + if (!meta.args.interaction.isContextMenu) return; + const inter = meta.args.interaction as ContextMenuInteraction; + await routeContextAction(meta.pluginData, inter); + }, +}); diff --git a/backend/src/plugins/ContextMenus/helpers.ts b/backend/src/plugins/ContextMenus/helpers.ts new file mode 100644 index 00000000..5a98f505 --- /dev/null +++ b/backend/src/plugins/ContextMenus/helpers.ts @@ -0,0 +1,25 @@ +import { ContextMenuInteraction } from "discord.js"; +import * as t from "io-ts"; +import { GuildPluginData } from "../../../../../Knub/dist"; +import { Awaitable } from "../../../../../Knub/dist/utils"; +import { ContextMenuPluginType } from "./types"; + +type ContextActionApplyFn = (meta: { + actionName: string; + pluginData: GuildPluginData; + actionConfig: TConfigType; + interaction: ContextMenuInteraction; +}) => Awaitable; + +export interface ContextActionBlueprint { + configType: TConfigType; + defaultConfig: Partial>; + + apply: ContextActionApplyFn>; +} + +export function contextMenuAction( + blueprint: ContextActionBlueprint, +): ContextActionBlueprint { + return blueprint; +} diff --git a/backend/src/plugins/ContextMenus/types.ts b/backend/src/plugins/ContextMenus/types.ts new file mode 100644 index 00000000..9b34cb6a --- /dev/null +++ b/backend/src/plugins/ContextMenus/types.ts @@ -0,0 +1,38 @@ +import * as t from "io-ts"; +import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub"; +import { GuildContextMenuLinks } from "../../data/GuildContextMenuLinks"; +import { tNullable } from "../../utils"; +import { AvailableActions } from "./actions/availableActions"; + +export enum ContextMenuTypes { + USER = 2, + MESSAGE = 3, +} + +export const ContextMenuTypeNameToNumber: Record = { + USER: 2, + MESSAGE: 3, +}; + +const ContextActionOpts = t.type({ + enabled: tNullable(t.boolean), + label: t.string, + type: t.keyof(ContextMenuTypes), + action: t.partial(AvailableActions.props), +}); +export type TContextActionOpts = t.TypeOf; + +export const ConfigSchema = t.type({ + context_actions: t.record(t.string, ContextActionOpts), +}); +export type TConfigSchema = t.TypeOf; + +export interface ContextMenuPluginType extends BasePluginType { + config: TConfigSchema; + state: { + contextMenuLinks: GuildContextMenuLinks; + }; +} + +export const contextMenuCmd = typedGuildCommand(); +export const contextMenuEvt = typedGuildEventListener(); diff --git a/backend/src/plugins/ContextMenus/utils/contextRouter.ts b/backend/src/plugins/ContextMenus/utils/contextRouter.ts new file mode 100644 index 00000000..c3fd3abe --- /dev/null +++ b/backend/src/plugins/ContextMenus/utils/contextRouter.ts @@ -0,0 +1,28 @@ +import { ContextMenuInteraction } from "discord.js"; +import { GuildPluginData } from "../../../../../../Knub/dist"; +import { availableActions } from "../actions/availableActions"; +import { ContextMenuPluginType } from "../types"; + +export async function routeContextAction( + pluginData: GuildPluginData, + interaction: ContextMenuInteraction, +) { + const contextLink = await pluginData.state.contextMenuLinks.get(interaction.commandId); + if (!contextLink) return; + const contextActions = Object.entries(pluginData.config.get().context_actions); + + const configLink = contextActions.find(x => x[0] === contextLink.action_name); + if (!configLink) return; + + for (const [actionName, actionConfig] of Object.entries(configLink[1].action)) { + if (actionConfig == null) return; + const action = availableActions[actionName]; + action.apply({ + actionName, + pluginData, + actionConfig, + interaction, + }); + return; + } +} diff --git a/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts b/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts new file mode 100644 index 00000000..a83cbd4a --- /dev/null +++ b/backend/src/plugins/ContextMenus/utils/loadAllCommands.ts @@ -0,0 +1,37 @@ +import { ApplicationCommandData } from "discord.js"; +import { LogType } from "src/data/LogType"; +import { LogsPlugin } from "src/plugins/Logs/LogsPlugin"; +import { GuildPluginData } from "../../../../../../Knub/dist"; +import { ContextMenuPluginType, ContextMenuTypeNameToNumber } from "../types"; + +export async function loadAllCommands(pluginData: GuildPluginData) { + const comms = await pluginData.client.application!.commands; + const actions = pluginData.config.get().context_actions; + const newCommands: ApplicationCommandData[] = []; + const addedNames: string[] = []; + + for (const [name, configAction] of Object.entries(actions)) { + if (!configAction.enabled) continue; + + const data: ApplicationCommandData = { + type: ContextMenuTypeNameToNumber[configAction.type], + name: configAction.label, + }; + addedNames.push(name); + newCommands.push(data); + } + + const setCommands = await comms.set(newCommands, pluginData.guild.id).catch(e => { + pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, `Unable to overwrite context menus: ${e}`); + return undefined; + }); + if (!setCommands) return; + + const setCommandsArray = [...setCommands.values()]; + await pluginData.state.contextMenuLinks.deleteAll(); + + for (let i = 0; i < setCommandsArray.length; i++) { + const command = setCommandsArray[i]; + pluginData.state.contextMenuLinks.create(command.id, addedNames[i]); + } +} diff --git a/backend/src/plugins/ContextMenus/utils/resolveActionContactMethods.ts b/backend/src/plugins/ContextMenus/utils/resolveActionContactMethods.ts new file mode 100644 index 00000000..bec64765 --- /dev/null +++ b/backend/src/plugins/ContextMenus/utils/resolveActionContactMethods.ts @@ -0,0 +1,32 @@ +import { Snowflake, TextChannel } from "discord.js"; +import { GuildPluginData } from "knub"; +import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; +import { disableUserNotificationStrings, UserNotificationMethod } from "../../../utils"; +import { ContextMenuPluginType } from "../types"; + +export function resolveActionContactMethods( + pluginData: GuildPluginData, + actionConfig: { + notify?: string | null; + notifyChannel?: string | null; + }, +): UserNotificationMethod[] { + if (actionConfig.notify === "dm") { + return [{ type: "dm" }]; + } else if (actionConfig.notify === "channel") { + if (!actionConfig.notifyChannel) { + throw new RecoverablePluginError(ERRORS.NO_USER_NOTIFICATION_CHANNEL); + } + + const channel = pluginData.guild.channels.cache.get(actionConfig.notifyChannel as Snowflake); + if (!(channel instanceof TextChannel)) { + throw new RecoverablePluginError(ERRORS.INVALID_USER_NOTIFICATION_CHANNEL); + } + + return [{ type: "channel", channel }]; + } else if (actionConfig.notify && disableUserNotificationStrings.includes(actionConfig.notify)) { + return []; + } + + return []; +} diff --git a/backend/src/plugins/Counters/CountersPlugin.ts b/backend/src/plugins/Counters/CountersPlugin.ts index 028c6a0d..d2566515 100644 --- a/backend/src/plugins/Counters/CountersPlugin.ts +++ b/backend/src/plugins/Counters/CountersPlugin.ts @@ -1,33 +1,32 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { ConfigSchema, CountersPluginType, TTrigger } from "./types"; -import { GuildCounters } from "../../data/GuildCounters"; -import { mapToPublicFn } from "../../pluginUtils"; -import { changeCounterValue } from "./functions/changeCounterValue"; -import { setCounterValue } from "./functions/setCounterValue"; -import { convertDelayStringToMS, MINUTES, SECONDS } from "../../utils"; import { EventEmitter } from "events"; -import { onCounterEvent } from "./functions/onCounterEvent"; -import { offCounterEvent } from "./functions/offCounterEvent"; -import { emitCounterEvent } from "./functions/emitCounterEvent"; -import { ConfigPreprocessorFn } from "knub/dist/config/configTypes"; -import { decayCounter } from "./functions/decayCounter"; -import { StrictValidationError } from "../../validatorUtils"; import { PluginOptions } from "knub"; -import { ViewCounterCmd } from "./commands/ViewCounterCmd"; -import { AddCounterCmd } from "./commands/AddCounterCmd"; -import { SetCounterCmd } from "./commands/SetCounterCmd"; +import { ConfigPreprocessorFn } from "knub/dist/config/configTypes"; import { buildCounterConditionString, CounterTrigger, getReverseCounterComparisonOp, parseCounterConditionString, } from "../../data/entities/CounterTrigger"; +import { GuildCounters } from "../../data/GuildCounters"; +import { mapToPublicFn } from "../../pluginUtils"; +import { convertDelayStringToMS, MINUTES } from "../../utils"; +import { StrictValidationError } from "../../validatorUtils"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import { AddCounterCmd } from "./commands/AddCounterCmd"; +import { CountersListCmd } from "./commands/CountersListCmd"; +import { ResetAllCounterValuesCmd } from "./commands/ResetAllCounterValuesCmd"; +import { ResetCounterCmd } from "./commands/ResetCounterCmd"; +import { SetCounterCmd } from "./commands/SetCounterCmd"; +import { ViewCounterCmd } from "./commands/ViewCounterCmd"; +import { changeCounterValue } from "./functions/changeCounterValue"; +import { counterExists } from "./functions/counterExists"; +import { decayCounter } from "./functions/decayCounter"; import { getPrettyNameForCounter } from "./functions/getPrettyNameForCounter"; import { getPrettyNameForCounterTrigger } from "./functions/getPrettyNameForCounterTrigger"; -import { counterExists } from "./functions/counterExists"; -import { ResetAllCounterValuesCmd } from "./commands/ResetAllCounterValuesCmd"; -import { CountersListCmd } from "./commands/CountersListCmd"; -import { ResetCounterCmd } from "./commands/ResetCounterCmd"; +import { offCounterEvent } from "./functions/offCounterEvent"; +import { onCounterEvent } from "./functions/onCounterEvent"; +import { setCounterValue } from "./functions/setCounterValue"; +import { ConfigSchema, CountersPluginType, TTrigger } from "./types"; const MAX_COUNTERS = 5; const MAX_TRIGGERS_PER_COUNTER = 5; diff --git a/backend/src/plugins/Counters/commands/AddCounterCmd.ts b/backend/src/plugins/Counters/commands/AddCounterCmd.ts index 0119c0cb..c045469f 100644 --- a/backend/src/plugins/Counters/commands/AddCounterCmd.ts +++ b/backend/src/plugins/Counters/commands/AddCounterCmd.ts @@ -1,11 +1,11 @@ +import { Snowflake, TextChannel } from "discord.js"; import { typedGuildCommand } from "knub"; -import { CountersPluginType } from "../types"; +import { waitForReply } from "knub/dist/helpers"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage } from "../../../pluginUtils"; -import { resolveChannel, waitForReply } from "knub/dist/helpers"; -import { TextChannel, User } from "eris"; import { resolveUser, UnknownUser } from "../../../utils"; import { changeCounterValue } from "../functions/changeCounterValue"; +import { CountersPluginType } from "../types"; export const AddCounterCmd = typedGuildCommand()({ trigger: ["counters add", "counter add", "addcounter"], @@ -66,14 +66,14 @@ export const AddCounterCmd = typedGuildCommand()({ let channel = args.channel; if (!channel && counter.per_channel) { - message.channel.createMessage(`Which channel's counter value would you like to add to?`); + message.channel.send(`Which channel's counter value would you like to add to?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { sendErrorMessage(pluginData, message.channel, "Cancelling"); return; } - const potentialChannel = resolveChannel(pluginData.guild, reply.content); + const potentialChannel = pluginData.guild.channels.resolve(reply.content as Snowflake); if (!potentialChannel || !(potentialChannel instanceof TextChannel)) { sendErrorMessage(pluginData, message.channel, "Channel is not a text channel, cancelling"); return; @@ -84,7 +84,7 @@ export const AddCounterCmd = typedGuildCommand()({ let user = args.user; if (!user && counter.per_user) { - message.channel.createMessage(`Which user's counter value would you like to add to?`); + message.channel.send(`Which user's counter value would you like to add to?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { sendErrorMessage(pluginData, message.channel, "Cancelling"); @@ -102,7 +102,7 @@ export const AddCounterCmd = typedGuildCommand()({ let amount = args.amount; if (!amount) { - message.channel.createMessage("How much would you like to add to the counter's value?"); + message.channel.send("How much would you like to add to the counter's value?"); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { sendErrorMessage(pluginData, message.channel, "Cancelling"); @@ -123,19 +123,15 @@ export const AddCounterCmd = typedGuildCommand()({ const counterName = counter.name || args.counterName; if (channel && user) { - message.channel.createMessage( + message.channel.send( `Added ${amount} to **${counterName}** for <@!${user.id}> in <#${channel.id}>. The value is now ${newValue}.`, ); } else if (channel) { - message.channel.createMessage( - `Added ${amount} to **${counterName}** in <#${channel.id}>. The value is now ${newValue}.`, - ); + message.channel.send(`Added ${amount} to **${counterName}** in <#${channel.id}>. The value is now ${newValue}.`); } else if (user) { - message.channel.createMessage( - `Added ${amount} to **${counterName}** for <@!${user.id}>. The value is now ${newValue}.`, - ); + message.channel.send(`Added ${amount} to **${counterName}** for <@!${user.id}>. The value is now ${newValue}.`); } else { - message.channel.createMessage(`Added ${amount} to **${counterName}**. The value is now ${newValue}.`); + message.channel.send(`Added ${amount} to **${counterName}**. The value is now ${newValue}.`); } }, }); diff --git a/backend/src/plugins/Counters/commands/CountersListCmd.ts b/backend/src/plugins/Counters/commands/CountersListCmd.ts index a9316268..c92a2fe4 100644 --- a/backend/src/plugins/Counters/commands/CountersListCmd.ts +++ b/backend/src/plugins/Counters/commands/CountersListCmd.ts @@ -1,8 +1,8 @@ import { typedGuildCommand } from "knub"; -import { CountersPluginType } from "../types"; import { sendErrorMessage } from "../../../pluginUtils"; import { trimMultilineString, ucfirst } from "../../../utils"; import { getGuildPrefix } from "../../../utils/getGuildPrefix"; +import { CountersPluginType } from "../types"; export const CountersListCmd = typedGuildCommand()({ trigger: ["counters list", "counter list", "counters"], @@ -41,7 +41,7 @@ export const CountersListCmd = typedGuildCommand()({ hintLines.push(`Use \`${getGuildPrefix(pluginData)}counters reset_all \` to reset a counter entirely`); } - message.channel.createMessage( + message.channel.send( trimMultilineString(` ${counterLines.join("\n\n")} diff --git a/backend/src/plugins/Counters/commands/ResetAllCounterValuesCmd.ts b/backend/src/plugins/Counters/commands/ResetAllCounterValuesCmd.ts index 50fa7483..af8dc9ad 100644 --- a/backend/src/plugins/Counters/commands/ResetAllCounterValuesCmd.ts +++ b/backend/src/plugins/Counters/commands/ResetAllCounterValuesCmd.ts @@ -1,14 +1,9 @@ import { typedGuildCommand } from "knub"; -import { CountersPluginType } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { resolveChannel, waitForReply } from "knub/dist/helpers"; -import { TextChannel, User } from "eris"; -import { confirm, MINUTES, noop, resolveUser, trimMultilineString, UnknownUser } from "../../../utils"; -import { changeCounterValue } from "../functions/changeCounterValue"; -import { setCounterValue } from "../functions/setCounterValue"; +import { confirm, noop, trimMultilineString } from "../../../utils"; import { resetAllCounterValues } from "../functions/resetAllCounterValues"; -import { counterIdLock } from "../../../utils/lockNameHelpers"; +import { CountersPluginType } from "../types"; export const ResetAllCounterValuesCmd = typedGuildCommand()({ trigger: ["counters reset_all"], @@ -33,28 +28,23 @@ export const ResetAllCounterValuesCmd = typedGuildCommand()( } const counterName = counter.name || args.counterName; - const confirmed = await confirm( - pluginData.client, - message.channel, - message.author.id, - trimMultilineString(` + const confirmed = await confirm(message.channel, message.author.id, { + content: trimMultilineString(` Do you want to reset **ALL** values for counter **${counterName}**? This will reset the counter for **all** users and channels. **Note:** This will *not* trigger any triggers or counter triggers. `), - ); + }); if (!confirmed) { sendErrorMessage(pluginData, message.channel, "Cancelled"); return; } const loadingMessage = await message.channel - .createMessage(`Resetting counter **${counterName}**. This might take a while. Please don't reload the config.`) + .send(`Resetting counter **${counterName}**. This might take a while. Please don't reload the config.`) .catch(() => null); - const lock = await pluginData.locks.acquire(counterIdLock(counterId), 10 * MINUTES); await resetAllCounterValues(pluginData, args.counterName); - lock.interrupt(); loadingMessage?.delete().catch(noop); sendSuccessMessage(pluginData, message.channel, `All counter values for **${counterName}** have been reset`); diff --git a/backend/src/plugins/Counters/commands/ResetCounterCmd.ts b/backend/src/plugins/Counters/commands/ResetCounterCmd.ts index 166aea66..2c92d329 100644 --- a/backend/src/plugins/Counters/commands/ResetCounterCmd.ts +++ b/backend/src/plugins/Counters/commands/ResetCounterCmd.ts @@ -1,11 +1,11 @@ +import { Snowflake, TextChannel } from "discord.js"; import { typedGuildCommand } from "knub"; -import { CountersPluginType } from "../types"; +import { waitForReply } from "knub/dist/helpers"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage } from "../../../pluginUtils"; -import { resolveChannel, waitForReply } from "knub/dist/helpers"; -import { TextChannel } from "eris"; import { resolveUser, UnknownUser } from "../../../utils"; import { setCounterValue } from "../functions/setCounterValue"; +import { CountersPluginType } from "../types"; export const ResetCounterCmd = typedGuildCommand()({ trigger: ["counters reset", "counter reset", "resetcounter"], @@ -61,14 +61,14 @@ export const ResetCounterCmd = typedGuildCommand()({ let channel = args.channel; if (!channel && counter.per_channel) { - message.channel.createMessage(`Which channel's counter value would you like to reset?`); + message.channel.send(`Which channel's counter value would you like to reset?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { sendErrorMessage(pluginData, message.channel, "Cancelling"); return; } - const potentialChannel = resolveChannel(pluginData.guild, reply.content); + const potentialChannel = pluginData.guild.channels.resolve(reply.content as Snowflake); if (!potentialChannel || !(potentialChannel instanceof TextChannel)) { sendErrorMessage(pluginData, message.channel, "Channel is not a text channel, cancelling"); return; @@ -79,7 +79,7 @@ export const ResetCounterCmd = typedGuildCommand()({ let user = args.user; if (!user && counter.per_user) { - message.channel.createMessage(`Which user's counter value would you like to reset?`); + message.channel.send(`Which user's counter value would you like to reset?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { sendErrorMessage(pluginData, message.channel, "Cancelling"); @@ -99,13 +99,13 @@ export const ResetCounterCmd = typedGuildCommand()({ const counterName = counter.name || args.counterName; if (channel && user) { - message.channel.createMessage(`Reset **${counterName}** for <@!${user.id}> in <#${channel.id}>`); + message.channel.send(`Reset **${counterName}** for <@!${user.id}> in <#${channel.id}>`); } else if (channel) { - message.channel.createMessage(`Reset **${counterName}** in <#${channel.id}>`); + message.channel.send(`Reset **${counterName}** in <#${channel.id}>`); } else if (user) { - message.channel.createMessage(`Reset **${counterName}** for <@!${user.id}>`); + message.channel.send(`Reset **${counterName}** for <@!${user.id}>`); } else { - message.channel.createMessage(`Reset **${counterName}**`); + message.channel.send(`Reset **${counterName}**`); } }, }); diff --git a/backend/src/plugins/Counters/commands/SetCounterCmd.ts b/backend/src/plugins/Counters/commands/SetCounterCmd.ts index 9c84e63f..5052c23c 100644 --- a/backend/src/plugins/Counters/commands/SetCounterCmd.ts +++ b/backend/src/plugins/Counters/commands/SetCounterCmd.ts @@ -1,12 +1,11 @@ +import { Snowflake, TextChannel } from "discord.js"; import { typedGuildCommand } from "knub"; -import { CountersPluginType } from "../types"; +import { waitForReply } from "knub/dist/helpers"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage } from "../../../pluginUtils"; -import { resolveChannel, waitForReply } from "knub/dist/helpers"; -import { TextChannel, User } from "eris"; import { resolveUser, UnknownUser } from "../../../utils"; -import { changeCounterValue } from "../functions/changeCounterValue"; import { setCounterValue } from "../functions/setCounterValue"; +import { CountersPluginType } from "../types"; export const SetCounterCmd = typedGuildCommand()({ trigger: ["counters set", "counter set", "setcounter"], @@ -67,14 +66,14 @@ export const SetCounterCmd = typedGuildCommand()({ let channel = args.channel; if (!channel && counter.per_channel) { - message.channel.createMessage(`Which channel's counter value would you like to change?`); + message.channel.send(`Which channel's counter value would you like to change?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { sendErrorMessage(pluginData, message.channel, "Cancelling"); return; } - const potentialChannel = resolveChannel(pluginData.guild, reply.content); + const potentialChannel = pluginData.guild.channels.resolve(reply.content as Snowflake); if (!potentialChannel || !(potentialChannel instanceof TextChannel)) { sendErrorMessage(pluginData, message.channel, "Channel is not a text channel, cancelling"); return; @@ -85,7 +84,7 @@ export const SetCounterCmd = typedGuildCommand()({ let user = args.user; if (!user && counter.per_user) { - message.channel.createMessage(`Which user's counter value would you like to change?`); + message.channel.send(`Which user's counter value would you like to change?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { sendErrorMessage(pluginData, message.channel, "Cancelling"); @@ -103,7 +102,7 @@ export const SetCounterCmd = typedGuildCommand()({ let value = args.value; if (!value) { - message.channel.createMessage("What would you like to set the counter's value to?"); + message.channel.send("What would you like to set the counter's value to?"); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { sendErrorMessage(pluginData, message.channel, "Cancelling"); @@ -128,13 +127,13 @@ export const SetCounterCmd = typedGuildCommand()({ const counterName = counter.name || args.counterName; if (channel && user) { - message.channel.createMessage(`Set **${counterName}** for <@!${user.id}> in <#${channel.id}> to ${value}`); + message.channel.send(`Set **${counterName}** for <@!${user.id}> in <#${channel.id}> to ${value}`); } else if (channel) { - message.channel.createMessage(`Set **${counterName}** in <#${channel.id}> to ${value}`); + message.channel.send(`Set **${counterName}** in <#${channel.id}> to ${value}`); } else if (user) { - message.channel.createMessage(`Set **${counterName}** for <@!${user.id}> to ${value}`); + message.channel.send(`Set **${counterName}** for <@!${user.id}> to ${value}`); } else { - message.channel.createMessage(`Set **${counterName}** to ${value}`); + message.channel.send(`Set **${counterName}** to ${value}`); } }, }); diff --git a/backend/src/plugins/Counters/commands/ViewCounterCmd.ts b/backend/src/plugins/Counters/commands/ViewCounterCmd.ts index 2a19c9f7..26a2fe7c 100644 --- a/backend/src/plugins/Counters/commands/ViewCounterCmd.ts +++ b/backend/src/plugins/Counters/commands/ViewCounterCmd.ts @@ -1,10 +1,10 @@ +import { Snowflake, TextChannel } from "discord.js"; import { typedGuildCommand } from "knub"; -import { CountersPluginType } from "../types"; +import { waitForReply } from "knub/dist/helpers"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage } from "../../../pluginUtils"; -import { resolveChannel, waitForReply } from "knub/dist/helpers"; -import { TextChannel, User } from "eris"; import { resolveUser, UnknownUser } from "../../../utils"; +import { CountersPluginType } from "../types"; export const ViewCounterCmd = typedGuildCommand()({ trigger: ["counters view", "counter view", "viewcounter", "counter"], @@ -60,14 +60,14 @@ export const ViewCounterCmd = typedGuildCommand()({ let channel = args.channel; if (!channel && counter.per_channel) { - message.channel.createMessage(`Which channel's counter value would you like to view?`); + message.channel.send(`Which channel's counter value would you like to view?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { sendErrorMessage(pluginData, message.channel, "Cancelling"); return; } - const potentialChannel = resolveChannel(pluginData.guild, reply.content); + const potentialChannel = pluginData.guild.channels.resolve(reply.content as Snowflake); if (!potentialChannel || !(potentialChannel instanceof TextChannel)) { sendErrorMessage(pluginData, message.channel, "Channel is not a text channel, cancelling"); return; @@ -78,7 +78,7 @@ export const ViewCounterCmd = typedGuildCommand()({ let user = args.user; if (!user && counter.per_user) { - message.channel.createMessage(`Which user's counter value would you like to view?`); + message.channel.send(`Which user's counter value would you like to view?`); const reply = await waitForReply(pluginData.client, message.channel, message.author.id); if (!reply || !reply.content) { sendErrorMessage(pluginData, message.channel, "Cancelling"); @@ -99,13 +99,13 @@ export const ViewCounterCmd = typedGuildCommand()({ const counterName = counter.name || args.counterName; if (channel && user) { - message.channel.createMessage(`**${counterName}** for <@!${user.id}> in <#${channel.id}> is ${finalValue}`); + message.channel.send(`**${counterName}** for <@!${user.id}> in <#${channel.id}> is ${finalValue}`); } else if (channel) { - message.channel.createMessage(`**${counterName}** in <#${channel.id}> is ${finalValue}`); + message.channel.send(`**${counterName}** in <#${channel.id}> is ${finalValue}`); } else if (user) { - message.channel.createMessage(`**${counterName}** for <@!${user.id}> is ${finalValue}`); + message.channel.send(`**${counterName}** for <@!${user.id}> is ${finalValue}`); } else { - message.channel.createMessage(`**${counterName}** is ${finalValue}`); + message.channel.send(`**${counterName}** is ${finalValue}`); } }, }); diff --git a/backend/src/plugins/Counters/functions/checkAllValuesForReverseTrigger.ts b/backend/src/plugins/Counters/functions/checkAllValuesForReverseTrigger.ts index 19998225..263855dc 100644 --- a/backend/src/plugins/Counters/functions/checkAllValuesForReverseTrigger.ts +++ b/backend/src/plugins/Counters/functions/checkAllValuesForReverseTrigger.ts @@ -1,6 +1,6 @@ import { GuildPluginData } from "knub"; -import { CountersPluginType } from "../types"; import { CounterTrigger } from "../../../data/entities/CounterTrigger"; +import { CountersPluginType } from "../types"; import { emitCounterEvent } from "./emitCounterEvent"; export async function checkAllValuesForReverseTrigger( diff --git a/backend/src/plugins/Counters/functions/checkAllValuesForTrigger.ts b/backend/src/plugins/Counters/functions/checkAllValuesForTrigger.ts index 02b02e5f..0a42673f 100644 --- a/backend/src/plugins/Counters/functions/checkAllValuesForTrigger.ts +++ b/backend/src/plugins/Counters/functions/checkAllValuesForTrigger.ts @@ -1,6 +1,6 @@ import { GuildPluginData } from "knub"; -import { CountersPluginType } from "../types"; import { CounterTrigger } from "../../../data/entities/CounterTrigger"; +import { CountersPluginType } from "../types"; import { emitCounterEvent } from "./emitCounterEvent"; export async function checkAllValuesForTrigger( diff --git a/backend/src/plugins/Counters/functions/checkCounterTrigger.ts b/backend/src/plugins/Counters/functions/checkCounterTrigger.ts index 5bdd1b05..8923abf5 100644 --- a/backend/src/plugins/Counters/functions/checkCounterTrigger.ts +++ b/backend/src/plugins/Counters/functions/checkCounterTrigger.ts @@ -1,6 +1,6 @@ import { GuildPluginData } from "knub"; -import { CountersPluginType } from "../types"; import { CounterTrigger } from "../../../data/entities/CounterTrigger"; +import { CountersPluginType } from "../types"; import { emitCounterEvent } from "./emitCounterEvent"; export async function checkCounterTrigger( diff --git a/backend/src/plugins/Counters/functions/checkReverseCounterTrigger.ts b/backend/src/plugins/Counters/functions/checkReverseCounterTrigger.ts index e601dd9d..544d4066 100644 --- a/backend/src/plugins/Counters/functions/checkReverseCounterTrigger.ts +++ b/backend/src/plugins/Counters/functions/checkReverseCounterTrigger.ts @@ -1,6 +1,6 @@ import { GuildPluginData } from "knub"; -import { CountersPluginType } from "../types"; import { CounterTrigger } from "../../../data/entities/CounterTrigger"; +import { CountersPluginType } from "../types"; import { emitCounterEvent } from "./emitCounterEvent"; export async function checkReverseCounterTrigger( diff --git a/backend/src/plugins/Counters/functions/decayCounter.ts b/backend/src/plugins/Counters/functions/decayCounter.ts index 7db4cef3..c7b813ea 100644 --- a/backend/src/plugins/Counters/functions/decayCounter.ts +++ b/backend/src/plugins/Counters/functions/decayCounter.ts @@ -1,8 +1,8 @@ import { GuildPluginData } from "knub"; -import { CountersPluginType } from "../types"; -import { checkAllValuesForTrigger } from "./checkAllValuesForTrigger"; -import { checkAllValuesForReverseTrigger } from "./checkAllValuesForReverseTrigger"; import { counterIdLock } from "../../../utils/lockNameHelpers"; +import { CountersPluginType } from "../types"; +import { checkAllValuesForReverseTrigger } from "./checkAllValuesForReverseTrigger"; +import { checkAllValuesForTrigger } from "./checkAllValuesForTrigger"; export async function decayCounter( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Counters/functions/emitCounterEvent.ts b/backend/src/plugins/Counters/functions/emitCounterEvent.ts index ad131e54..30e4bfea 100644 --- a/backend/src/plugins/Counters/functions/emitCounterEvent.ts +++ b/backend/src/plugins/Counters/functions/emitCounterEvent.ts @@ -1,5 +1,5 @@ -import { CounterEvents, CountersPluginType } from "../types"; import { GuildPluginData } from "knub"; +import { CounterEvents, CountersPluginType } from "../types"; export function emitCounterEvent( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Counters/functions/getPrettyNameForCounter.ts b/backend/src/plugins/Counters/functions/getPrettyNameForCounter.ts index 766b4ef3..c9fd0342 100644 --- a/backend/src/plugins/Counters/functions/getPrettyNameForCounter.ts +++ b/backend/src/plugins/Counters/functions/getPrettyNameForCounter.ts @@ -1,5 +1,5 @@ -import { CountersPluginType } from "../types"; import { GuildPluginData } from "knub"; +import { CountersPluginType } from "../types"; export function getPrettyNameForCounter(pluginData: GuildPluginData, counterName: string) { const config = pluginData.config.get(); diff --git a/backend/src/plugins/Counters/functions/getPrettyNameForCounterTrigger.ts b/backend/src/plugins/Counters/functions/getPrettyNameForCounterTrigger.ts index d7a2b923..1445bdd8 100644 --- a/backend/src/plugins/Counters/functions/getPrettyNameForCounterTrigger.ts +++ b/backend/src/plugins/Counters/functions/getPrettyNameForCounterTrigger.ts @@ -1,5 +1,5 @@ -import { CountersPluginType, TTrigger } from "../types"; import { GuildPluginData } from "knub"; +import { CountersPluginType, TTrigger } from "../types"; export function getPrettyNameForCounterTrigger( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Counters/functions/offCounterEvent.ts b/backend/src/plugins/Counters/functions/offCounterEvent.ts index 66ee9624..08c28cff 100644 --- a/backend/src/plugins/Counters/functions/offCounterEvent.ts +++ b/backend/src/plugins/Counters/functions/offCounterEvent.ts @@ -1,5 +1,5 @@ -import { CounterEventEmitter, CountersPluginType } from "../types"; import { GuildPluginData } from "knub"; +import { CounterEventEmitter, CountersPluginType } from "../types"; export function offCounterEvent( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Counters/functions/onCounterEvent.ts b/backend/src/plugins/Counters/functions/onCounterEvent.ts index 1a3aa6fd..1789571b 100644 --- a/backend/src/plugins/Counters/functions/onCounterEvent.ts +++ b/backend/src/plugins/Counters/functions/onCounterEvent.ts @@ -1,5 +1,5 @@ -import { CounterEvents, CountersPluginType } from "../types"; import { GuildPluginData } from "knub"; +import { CounterEvents, CountersPluginType } from "../types"; export function onCounterEvent( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Counters/types.ts b/backend/src/plugins/Counters/types.ts index 1efdc474..57bc69d6 100644 --- a/backend/src/plugins/Counters/types.ts +++ b/backend/src/plugins/Counters/types.ts @@ -1,9 +1,9 @@ +import { EventEmitter } from "events"; import * as t from "io-ts"; import { BasePluginType } from "knub"; +import { CounterTrigger } from "../../data/entities/CounterTrigger"; import { GuildCounters } from "../../data/GuildCounters"; import { tDelayString, tNullable } from "../../utils"; -import { EventEmitter } from "events"; -import { CounterTrigger } from "../../data/entities/CounterTrigger"; import Timeout = NodeJS.Timeout; export const Trigger = t.type({ diff --git a/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts b/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts index 43d25250..c1c183eb 100644 --- a/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts +++ b/backend/src/plugins/CustomEvents/CustomEventsPlugin.ts @@ -1,9 +1,9 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { ConfigSchema, CustomEventsPluginType } from "./types"; -import { typedGuildCommand, parseSignature } from "knub"; +import { parseSignature, typedGuildCommand } from "knub"; import { commandTypes } from "../../commandTypes"; import { stripObjectToScalars } from "../../utils"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { runEvent } from "./functions/runEvent"; +import { ConfigSchema, CustomEventsPluginType } from "./types"; const defaultOptions = { config: { diff --git a/backend/src/plugins/CustomEvents/actions/addRoleAction.ts b/backend/src/plugins/CustomEvents/actions/addRoleAction.ts index 77e28878..6621763d 100644 --- a/backend/src/plugins/CustomEvents/actions/addRoleAction.ts +++ b/backend/src/plugins/CustomEvents/actions/addRoleAction.ts @@ -1,11 +1,11 @@ -import { GuildPluginData } from "knub"; -import { CustomEventsPluginType, TCustomEvent } from "../types"; +import { Snowflake } from "discord.js"; import * as t from "io-ts"; +import { GuildPluginData } from "knub"; +import { canActOn } from "../../../pluginUtils"; import { renderTemplate } from "../../../templateFormatter"; import { resolveMember } from "../../../utils"; import { ActionError } from "../ActionError"; -import { canActOn } from "../../../pluginUtils"; -import { Message } from "eris"; +import { CustomEventsPluginType, TCustomEvent } from "../types"; export const AddRoleAction = t.type({ type: t.literal("add_role"), @@ -31,6 +31,6 @@ export async function addRoleAction( const rolesToAdd = Array.isArray(action.role) ? action.role : [action.role]; await target.edit({ - roles: Array.from(new Set([...target.roles, ...rolesToAdd])), + roles: Array.from(new Set([...target.roles.cache.values(), ...rolesToAdd])) as Snowflake[], }); } diff --git a/backend/src/plugins/CustomEvents/actions/createCaseAction.ts b/backend/src/plugins/CustomEvents/actions/createCaseAction.ts index 985ff14c..e9e4fac1 100644 --- a/backend/src/plugins/CustomEvents/actions/createCaseAction.ts +++ b/backend/src/plugins/CustomEvents/actions/createCaseAction.ts @@ -1,10 +1,10 @@ -import { GuildPluginData } from "knub"; -import { CustomEventsPluginType, TCustomEvent } from "../types"; import * as t from "io-ts"; -import { renderTemplate } from "../../../templateFormatter"; +import { GuildPluginData } from "knub"; import { CaseTypes } from "../../../data/CaseTypes"; -import { ActionError } from "../ActionError"; +import { renderTemplate } from "../../../templateFormatter"; import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { ActionError } from "../ActionError"; +import { CustomEventsPluginType, TCustomEvent } from "../types"; export const CreateCaseAction = t.type({ type: t.literal("create_case"), diff --git a/backend/src/plugins/CustomEvents/actions/makeRoleMentionableAction.ts b/backend/src/plugins/CustomEvents/actions/makeRoleMentionableAction.ts index 90ddd45b..11974a7a 100644 --- a/backend/src/plugins/CustomEvents/actions/makeRoleMentionableAction.ts +++ b/backend/src/plugins/CustomEvents/actions/makeRoleMentionableAction.ts @@ -1,8 +1,9 @@ -import { GuildPluginData } from "knub"; -import { CustomEventsPluginType, TCustomEvent } from "../types"; +import { Snowflake } from "discord.js"; import * as t from "io-ts"; +import { GuildPluginData } from "knub"; import { convertDelayStringToMS, noop, tDelayString } from "../../../utils"; import { ActionError } from "../ActionError"; +import { CustomEventsPluginType, TCustomEvent } from "../types"; export const MakeRoleMentionableAction = t.type({ type: t.literal("make_role_mentionable"), @@ -18,7 +19,7 @@ export async function makeRoleMentionableAction( event: TCustomEvent, eventData: any, ) { - const role = pluginData.guild.roles.get(action.role); + const role = pluginData.guild.roles.cache.get(action.role as Snowflake); if (!role) { throw new ActionError(`Unknown role: ${role}`); } diff --git a/backend/src/plugins/CustomEvents/actions/makeRoleUnmentionableAction.ts b/backend/src/plugins/CustomEvents/actions/makeRoleUnmentionableAction.ts index 0ceef330..505cb3f7 100644 --- a/backend/src/plugins/CustomEvents/actions/makeRoleUnmentionableAction.ts +++ b/backend/src/plugins/CustomEvents/actions/makeRoleUnmentionableAction.ts @@ -1,7 +1,8 @@ -import { GuildPluginData } from "knub"; -import { CustomEventsPluginType, TCustomEvent } from "../types"; +import { Snowflake } from "discord.js"; import * as t from "io-ts"; +import { GuildPluginData } from "knub"; import { ActionError } from "../ActionError"; +import { CustomEventsPluginType, TCustomEvent } from "../types"; export const MakeRoleUnmentionableAction = t.type({ type: t.literal("make_role_unmentionable"), @@ -16,7 +17,7 @@ export async function makeRoleUnmentionableAction( event: TCustomEvent, eventData: any, ) { - const role = pluginData.guild.roles.get(action.role); + const role = pluginData.guild.roles.cache.get(action.role as Snowflake); if (!role) { throw new ActionError(`Unknown role: ${role}`); } diff --git a/backend/src/plugins/CustomEvents/actions/messageAction.ts b/backend/src/plugins/CustomEvents/actions/messageAction.ts index f137d6b0..90630f1f 100644 --- a/backend/src/plugins/CustomEvents/actions/messageAction.ts +++ b/backend/src/plugins/CustomEvents/actions/messageAction.ts @@ -1,9 +1,9 @@ -import { GuildPluginData } from "knub"; -import { CustomEventsPluginType } from "../types"; +import { Snowflake, TextChannel } from "discord.js"; import * as t from "io-ts"; +import { GuildPluginData } from "knub"; import { renderTemplate } from "../../../templateFormatter"; import { ActionError } from "../ActionError"; -import { TextChannel } from "eris"; +import { CustomEventsPluginType } from "../types"; export const MessageAction = t.type({ type: t.literal("message"), @@ -18,9 +18,9 @@ export async function messageAction( values: any, ) { const targetChannelId = await renderTemplate(action.channel, values, false); - const targetChannel = pluginData.guild.channels.get(targetChannelId); + const targetChannel = pluginData.guild.channels.cache.get(targetChannelId as Snowflake); if (!targetChannel) throw new ActionError("Unknown target channel"); if (!(targetChannel instanceof TextChannel)) throw new ActionError("Target channel is not a text channel"); - await targetChannel.createMessage({ content: action.content }); + await targetChannel.send({ content: action.content }); } diff --git a/backend/src/plugins/CustomEvents/actions/moveToVoiceChannelAction.ts b/backend/src/plugins/CustomEvents/actions/moveToVoiceChannelAction.ts index d138c61d..08507bac 100644 --- a/backend/src/plugins/CustomEvents/actions/moveToVoiceChannelAction.ts +++ b/backend/src/plugins/CustomEvents/actions/moveToVoiceChannelAction.ts @@ -1,11 +1,11 @@ -import { GuildPluginData } from "knub"; -import { CustomEventsPluginType, TCustomEvent } from "../types"; +import { Snowflake, VoiceChannel } from "discord.js"; import * as t from "io-ts"; +import { GuildPluginData } from "knub"; +import { canActOn } from "../../../pluginUtils"; import { renderTemplate } from "../../../templateFormatter"; import { resolveMember } from "../../../utils"; import { ActionError } from "../ActionError"; -import { canActOn } from "../../../pluginUtils"; -import { Message, VoiceChannel } from "eris"; +import { CustomEventsPluginType, TCustomEvent } from "../types"; export const MoveToVoiceChannelAction = t.type({ type: t.literal("move_to_vc"), @@ -30,12 +30,12 @@ export async function moveToVoiceChannelAction( } const targetChannelId = await renderTemplate(action.channel, values, false); - const targetChannel = pluginData.guild.channels.get(targetChannelId); + const targetChannel = pluginData.guild.channels.cache.get(targetChannelId as Snowflake); if (!targetChannel) throw new ActionError("Unknown target channel"); if (!(targetChannel instanceof VoiceChannel)) throw new ActionError("Target channel is not a voice channel"); - if (!target.voiceState.channelID) return; + if (!target.voice.channelId) return; await target.edit({ - channelID: targetChannel.id, + channel: targetChannel.id, }); } diff --git a/backend/src/plugins/CustomEvents/actions/setChannelPermissionOverrides.ts b/backend/src/plugins/CustomEvents/actions/setChannelPermissionOverrides.ts index 595a56e2..80acd61c 100644 --- a/backend/src/plugins/CustomEvents/actions/setChannelPermissionOverrides.ts +++ b/backend/src/plugins/CustomEvents/actions/setChannelPermissionOverrides.ts @@ -1,7 +1,8 @@ -import { GuildPluginData } from "knub"; -import { CustomEventsPluginType, TCustomEvent } from "../types"; +import { Permissions, Snowflake, TextChannel } from "discord.js"; import * as t from "io-ts"; +import { GuildPluginData } from "knub"; import { ActionError } from "../ActionError"; +import { CustomEventsPluginType, TCustomEvent } from "../types"; export const SetChannelPermissionOverridesAction = t.type({ type: t.literal("set_channel_permission_overrides"), @@ -24,18 +25,22 @@ export async function setChannelPermissionOverridesAction( event: TCustomEvent, eventData: any, ) { - const channel = pluginData.guild.channels.get(action.channel); + const channel = pluginData.guild.channels.cache.get(action.channel as Snowflake) as TextChannel; if (!channel) { throw new ActionError(`Unknown channel: ${action.channel}`); } for (const override of action.overrides) { - await channel.editPermission( - override.id, - override.allow, - override.deny, - override.type, + channel.permissionOverwrites.create( + override.id as Snowflake, + new Permissions(BigInt(override.allow)).add(BigInt(override.deny)).serialize(), + ); + + /* + await channel.permissionOverwrites overwritePermissions( + [{ id: override.id, allow: BigInt(override.allow), deny: BigInt(override.deny), type: override.type }], `Custom event: ${event.name}`, ); + */ } } diff --git a/backend/src/plugins/CustomEvents/functions/runEvent.ts b/backend/src/plugins/CustomEvents/functions/runEvent.ts index 3b304ff9..1322d41b 100644 --- a/backend/src/plugins/CustomEvents/functions/runEvent.ts +++ b/backend/src/plugins/CustomEvents/functions/runEvent.ts @@ -1,15 +1,15 @@ +import { Message, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { CustomEventsPluginType, TCustomEvent } from "../types"; import { sendErrorMessage } from "../../../pluginUtils"; import { ActionError } from "../ActionError"; -import { Message } from "eris"; import { addRoleAction } from "../actions/addRoleAction"; import { createCaseAction } from "../actions/createCaseAction"; -import { moveToVoiceChannelAction } from "../actions/moveToVoiceChannelAction"; -import { messageAction } from "../actions/messageAction"; import { makeRoleMentionableAction } from "../actions/makeRoleMentionableAction"; import { makeRoleUnmentionableAction } from "../actions/makeRoleUnmentionableAction"; +import { messageAction } from "../actions/messageAction"; +import { moveToVoiceChannelAction } from "../actions/moveToVoiceChannelAction"; import { setChannelPermissionOverridesAction } from "../actions/setChannelPermissionOverrides"; +import { CustomEventsPluginType, TCustomEvent } from "../types"; export async function runEvent( pluginData: GuildPluginData, @@ -38,7 +38,7 @@ export async function runEvent( } catch (e) { if (e instanceof ActionError) { if (event.trigger.type === "command") { - sendErrorMessage(pluginData, (eventData.msg as Message).channel, e.message); + sendErrorMessage(pluginData, (eventData.msg as Message).channel as TextChannel, e.message); } else { // TODO: Where to log action errors from other kinds of triggers? } diff --git a/backend/src/plugins/CustomEvents/types.ts b/backend/src/plugins/CustomEvents/types.ts index 44058777..48628376 100644 --- a/backend/src/plugins/CustomEvents/types.ts +++ b/backend/src/plugins/CustomEvents/types.ts @@ -2,10 +2,10 @@ import * as t from "io-ts"; import { BasePluginType } from "knub"; import { AddRoleAction } from "./actions/addRoleAction"; import { CreateCaseAction } from "./actions/createCaseAction"; -import { MoveToVoiceChannelAction } from "./actions/moveToVoiceChannelAction"; -import { MessageAction } from "./actions/messageAction"; import { MakeRoleMentionableAction } from "./actions/makeRoleMentionableAction"; import { MakeRoleUnmentionableAction } from "./actions/makeRoleUnmentionableAction"; +import { MessageAction } from "./actions/messageAction"; +import { MoveToVoiceChannelAction } from "./actions/moveToVoiceChannelAction"; import { SetChannelPermissionOverridesAction } from "./actions/setChannelPermissionOverrides"; // Triggers diff --git a/backend/src/plugins/GuildAccessMonitor/GuildAccessMonitorPlugin.ts b/backend/src/plugins/GuildAccessMonitor/GuildAccessMonitorPlugin.ts index 62b77c29..a8fc7432 100644 --- a/backend/src/plugins/GuildAccessMonitor/GuildAccessMonitorPlugin.ts +++ b/backend/src/plugins/GuildAccessMonitor/GuildAccessMonitorPlugin.ts @@ -1,8 +1,8 @@ -import { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint"; -import { BasePluginType, typedGlobalEventListener, GlobalPluginData } from "knub"; +import { Guild } from "discord.js"; import * as t from "io-ts"; +import { BasePluginType, GlobalPluginData, typedGlobalEventListener } from "knub"; import { AllowedGuilds } from "../../data/AllowedGuilds"; -import { Guild } from "eris"; +import { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint"; interface GuildAccessMonitorPluginType extends BasePluginType { config: {}; @@ -28,7 +28,7 @@ export const GuildAccessMonitorPlugin = zeppelinGlobalPlugin()({ - event: "guildAvailable", + event: "guildCreate", listener({ pluginData, args: { guild } }) { checkGuild(pluginData, guild); }, @@ -40,7 +40,7 @@ export const GuildAccessMonitorPlugin = zeppelinGlobalPlugin()({ name: "guild_config_reloader", diff --git a/backend/src/plugins/GuildConfigReloader/functions/reloadChangedGuilds.ts b/backend/src/plugins/GuildConfigReloader/functions/reloadChangedGuilds.ts index 10cd97c0..bc7eb6e9 100644 --- a/backend/src/plugins/GuildConfigReloader/functions/reloadChangedGuilds.ts +++ b/backend/src/plugins/GuildConfigReloader/functions/reloadChangedGuilds.ts @@ -1,6 +1,7 @@ +import { Snowflake } from "discord.js"; import { GlobalPluginData } from "knub"; -import { GuildConfigReloaderPluginType } from "../types"; import { SECONDS } from "../../../utils"; +import { GuildConfigReloaderPluginType } from "../types"; const CHECK_INTERVAL = 1 * SECONDS; @@ -11,7 +12,7 @@ export async function reloadChangedGuilds(pluginData: GlobalPluginData()({ name: "guild_info_saver", @@ -29,7 +29,7 @@ function updateGuildInfo(pluginData: GuildPluginData) pluginData.state.allowedGuilds.updateInfo( pluginData.guild.id, pluginData.guild.name, - pluginData.guild.iconURL, - pluginData.guild.ownerID, + pluginData.guild.iconURL(), + pluginData.guild.ownerId, ); } diff --git a/backend/src/plugins/LocateUser/LocateUserPlugin.ts b/backend/src/plugins/LocateUser/LocateUserPlugin.ts index ed5c6567..fc77d99a 100644 --- a/backend/src/plugins/LocateUser/LocateUserPlugin.ts +++ b/backend/src/plugins/LocateUser/LocateUserPlugin.ts @@ -1,15 +1,15 @@ import { PluginOptions } from "knub"; -import { ConfigSchema, LocateUserPluginType } from "./types"; -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { GuildVCAlerts } from "../../data/GuildVCAlerts"; -import { outdatedAlertsLoop } from "./utils/outdatedLoop"; -import { fillActiveAlertsList } from "./utils/fillAlertsList"; -import { WhereCmd } from "./commands/WhereCmd"; +import { trimPluginDescription } from "../../utils"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { FollowCmd } from "./commands/FollowCmd"; import { DeleteFollowCmd, ListFollowCmd } from "./commands/ListFollowCmd"; -import { ChannelJoinAlertsEvt, ChannelLeaveAlertsEvt, ChannelSwitchAlertsEvt } from "./events/SendAlertsEvts"; +import { WhereCmd } from "./commands/WhereCmd"; import { GuildBanRemoveAlertsEvt } from "./events/BanRemoveAlertsEvt"; -import { trimPluginDescription } from "../../utils"; +import { VoiceStateUpdateAlertEvt } from "./events/SendAlertsEvts"; +import { ConfigSchema, LocateUserPluginType } from "./types"; +import { fillActiveAlertsList } from "./utils/fillAlertsList"; +import { outdatedAlertsLoop } from "./utils/outdatedLoop"; import Timeout = NodeJS.Timeout; const defaultOptions: PluginOptions = { @@ -53,9 +53,7 @@ export const LocateUserPlugin = zeppelinGuildPlugin()({ // prettier-ignore events: [ - ChannelJoinAlertsEvt, - ChannelSwitchAlertsEvt, - ChannelLeaveAlertsEvt, + VoiceStateUpdateAlertEvt, GuildBanRemoveAlertsEvt ], diff --git a/backend/src/plugins/LocateUser/commands/FollowCmd.ts b/backend/src/plugins/LocateUser/commands/FollowCmd.ts index 08c105ed..5a8d7e5d 100644 --- a/backend/src/plugins/LocateUser/commands/FollowCmd.ts +++ b/backend/src/plugins/LocateUser/commands/FollowCmd.ts @@ -1,9 +1,9 @@ -import { locateUserCmd } from "../types"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import moment from "moment-timezone"; import humanizeDuration from "humanize-duration"; -import { MINUTES, SECONDS } from "../../../utils"; +import moment from "moment-timezone"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { MINUTES, SECONDS } from "../../../utils"; +import { locateUserCmd } from "../types"; export const FollowCmd = locateUserCmd({ trigger: ["follow", "f"], @@ -46,7 +46,7 @@ export const FollowCmd = locateUserCmd({ sendSuccessMessage( pluginData, msg.channel, - `Every time ${args.member.mention} joins or switches VC in the next ${humanizeDuration( + `Every time <@${args.member.id}> joins or switches VC in the next ${humanizeDuration( time, )} i will notify and move you.\nPlease make sure to be in a voice channel, otherwise i cannot move you!`, ); @@ -54,9 +54,7 @@ export const FollowCmd = locateUserCmd({ sendSuccessMessage( pluginData, msg.channel, - `Every time ${args.member.mention} joins or switches VC in the next ${humanizeDuration( - time, - )} i will notify you`, + `Every time <@${args.member.id}> joins or switches VC in the next ${humanizeDuration(time)} i will notify you`, ); } }, diff --git a/backend/src/plugins/LocateUser/commands/ListFollowCmd.ts b/backend/src/plugins/LocateUser/commands/ListFollowCmd.ts index 67ad383d..f8e669bc 100644 --- a/backend/src/plugins/LocateUser/commands/ListFollowCmd.ts +++ b/backend/src/plugins/LocateUser/commands/ListFollowCmd.ts @@ -1,7 +1,7 @@ -import { locateUserCmd } from "../types"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { createChunkedMessage, sorter } from "../../../utils"; +import { locateUserCmd } from "../types"; export const ListFollowCmd = locateUserCmd({ trigger: ["follows", "fs"], diff --git a/backend/src/plugins/LocateUser/commands/WhereCmd.ts b/backend/src/plugins/LocateUser/commands/WhereCmd.ts index 0662e880..0ff00d91 100644 --- a/backend/src/plugins/LocateUser/commands/WhereCmd.ts +++ b/backend/src/plugins/LocateUser/commands/WhereCmd.ts @@ -1,6 +1,5 @@ -import { locateUserCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { resolveMember } from "../../../utils"; +import { locateUserCmd } from "../types"; import { sendWhere } from "../utils/sendWhere"; export const WhereCmd = locateUserCmd({ @@ -14,6 +13,6 @@ export const WhereCmd = locateUserCmd({ }, async run({ message: msg, args, pluginData }) { - sendWhere(pluginData, args.member, msg.channel, `${msg.member.mention} | `); + sendWhere(pluginData, args.member, msg.channel, `<@${msg.member.id}> | `); }, }); diff --git a/backend/src/plugins/LocateUser/events/BanRemoveAlertsEvt.ts b/backend/src/plugins/LocateUser/events/BanRemoveAlertsEvt.ts index cc6c155f..bc4e8d0f 100644 --- a/backend/src/plugins/LocateUser/events/BanRemoveAlertsEvt.ts +++ b/backend/src/plugins/LocateUser/events/BanRemoveAlertsEvt.ts @@ -4,7 +4,7 @@ export const GuildBanRemoveAlertsEvt = locateUserEvt({ event: "guildBanAdd", async listener(meta) { - const alerts = await meta.pluginData.state.alerts.getAlertsByUserId(meta.args.user.id); + const alerts = await meta.pluginData.state.alerts.getAlertsByUserId(meta.args.ban.user.id); alerts.forEach(alert => { meta.pluginData.state.alerts.delete(alert.id); }); diff --git a/backend/src/plugins/LocateUser/events/SendAlertsEvts.ts b/backend/src/plugins/LocateUser/events/SendAlertsEvts.ts index e426e704..552fb8ef 100644 --- a/backend/src/plugins/LocateUser/events/SendAlertsEvts.ts +++ b/backend/src/plugins/LocateUser/events/SendAlertsEvts.ts @@ -1,39 +1,28 @@ +import { Snowflake, TextChannel } from "discord.js"; import { locateUserEvt } from "../types"; import { sendAlerts } from "../utils/sendAlerts"; -import { TextableChannel, VoiceChannel } from "eris"; -export const ChannelJoinAlertsEvt = locateUserEvt({ - event: "voiceChannelJoin", +export const VoiceStateUpdateAlertEvt = locateUserEvt({ + event: "voiceStateUpdate", async listener(meta) { - if (meta.pluginData.state.usersWithAlerts.includes(meta.args.member.id)) { - sendAlerts(meta.pluginData, meta.args.member.id); + const memberId = meta.args.oldState.member ? meta.args.oldState.member.id : meta.args.newState.member!.id; + + if (meta.args.newState.channel != null) { + if (meta.pluginData.state.usersWithAlerts.includes(memberId)) { + sendAlerts(meta.pluginData, memberId); + } + } else { + const triggeredAlerts = await meta.pluginData.state.alerts.getAlertsByUserId(memberId); + const voiceChannel = meta.args.oldState.channel!; + + triggeredAlerts.forEach(alert => { + const txtChannel = meta.pluginData.guild.channels.resolve(alert.channel_id as Snowflake) as TextChannel; + txtChannel.send({ + content: `🔴 <@!${alert.requestor_id}> the user <@!${alert.user_id}> disconnected out of \`${voiceChannel.name}\``, + allowedMentions: { users: [alert.requestor_id as Snowflake] }, + }); + }); } }, }); - -export const ChannelSwitchAlertsEvt = locateUserEvt({ - event: "voiceChannelSwitch", - - async listener(meta) { - if (meta.pluginData.state.usersWithAlerts.includes(meta.args.member.id)) { - sendAlerts(meta.pluginData, meta.args.member.id); - } - }, -}); - -export const ChannelLeaveAlertsEvt = locateUserEvt({ - event: "voiceChannelLeave", - - async listener(meta) { - const triggeredAlerts = await meta.pluginData.state.alerts.getAlertsByUserId(meta.args.member.id); - const voiceChannel = meta.args.oldChannel as VoiceChannel; - - triggeredAlerts.forEach(alert => { - const txtChannel = meta.pluginData.client.getChannel(alert.channel_id) as TextableChannel; - txtChannel.createMessage( - `🔴 <@!${alert.requestor_id}> the user <@!${alert.user_id}> disconnected out of \`${voiceChannel.name}\``, - ); - }); - }, -}); diff --git a/backend/src/plugins/LocateUser/utils/createOrReuseInvite.ts b/backend/src/plugins/LocateUser/utils/createOrReuseInvite.ts index 9f453e21..3ac5dd1d 100644 --- a/backend/src/plugins/LocateUser/utils/createOrReuseInvite.ts +++ b/backend/src/plugins/LocateUser/utils/createOrReuseInvite.ts @@ -1,11 +1,11 @@ -import { VoiceChannel } from "eris"; +import { VoiceChannel } from "discord.js"; export async function createOrReuseInvite(vc: VoiceChannel) { - const existingInvites = await vc.getInvites(); + const existingInvites = await vc.fetchInvites(); - if (existingInvites.length !== 0) { - return existingInvites[0]; + if (existingInvites.size !== 0) { + return existingInvites.first()!; } else { - return vc.createInvite(undefined); + return vc.createInvite(); } } diff --git a/backend/src/plugins/LocateUser/utils/moveMember.ts b/backend/src/plugins/LocateUser/utils/moveMember.ts index e3c75b16..d3c85ecd 100644 --- a/backend/src/plugins/LocateUser/utils/moveMember.ts +++ b/backend/src/plugins/LocateUser/utils/moveMember.ts @@ -1,19 +1,19 @@ -import { Member, TextableChannel } from "eris"; +import { GuildMember, Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { LocateUserPluginType } from "../types"; import { sendErrorMessage } from "../../../pluginUtils"; +import { LocateUserPluginType } from "../types"; export async function moveMember( pluginData: GuildPluginData, toMoveID: string, - target: Member, - errorChannel: TextableChannel, + target: GuildMember, + errorChannel: TextChannel, ) { - const modMember: Member = await pluginData.client.getRESTGuildMember(pluginData.guild.id, toMoveID); - if (modMember.voiceState.channelID != null) { + const modMember: GuildMember = await pluginData.guild.members.fetch(toMoveID as Snowflake); + if (modMember.voice.channelId != null) { try { await modMember.edit({ - channelID: target.voiceState.channelID, + channel: target.voice.channelId, }); } catch { sendErrorMessage(pluginData, errorChannel, "Failed to move you. Are you in a voice channel?"); diff --git a/backend/src/plugins/LocateUser/utils/outdatedLoop.ts b/backend/src/plugins/LocateUser/utils/outdatedLoop.ts index 0e3f29cd..4236342d 100644 --- a/backend/src/plugins/LocateUser/utils/outdatedLoop.ts +++ b/backend/src/plugins/LocateUser/utils/outdatedLoop.ts @@ -1,7 +1,7 @@ -import { SECONDS } from "../../../utils"; -import { removeUserIdFromActiveAlerts } from "./removeUserIdFromActiveAlerts"; import { GuildPluginData } from "knub"; +import { SECONDS } from "../../../utils"; import { LocateUserPluginType } from "../types"; +import { removeUserIdFromActiveAlerts } from "./removeUserIdFromActiveAlerts"; const ALERT_LOOP_TIME = 30 * SECONDS; diff --git a/backend/src/plugins/LocateUser/utils/sendAlerts.ts b/backend/src/plugins/LocateUser/utils/sendAlerts.ts index df49c3d1..67f865bf 100644 --- a/backend/src/plugins/LocateUser/utils/sendAlerts.ts +++ b/backend/src/plugins/LocateUser/utils/sendAlerts.ts @@ -1,9 +1,9 @@ +import { Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { LocateUserPluginType } from "../types"; import { resolveMember } from "../../../utils"; -import { sendWhere } from "./sendWhere"; -import { TextableChannel } from "eris"; +import { LocateUserPluginType } from "../types"; import { moveMember } from "./moveMember"; +import { sendWhere } from "./sendWhere"; export async function sendAlerts(pluginData: GuildPluginData, userId: string) { const triggeredAlerts = await pluginData.state.alerts.getAlertsByUserId(userId); @@ -12,7 +12,7 @@ export async function sendAlerts(pluginData: GuildPluginData { const prepend = `<@!${alert.requestor_id}>, an alert requested by you has triggered!\nReminder: \`${alert.body}\`\n`; - const txtChannel = pluginData.client.getChannel(alert.channel_id) as TextableChannel; + const txtChannel = pluginData.guild.channels.resolve(alert.channel_id as Snowflake) as TextChannel; sendWhere(pluginData, member, txtChannel, prepend); if (alert.active) { moveMember(pluginData, alert.requestor_id, member, txtChannel); diff --git a/backend/src/plugins/LocateUser/utils/sendWhere.ts b/backend/src/plugins/LocateUser/utils/sendWhere.ts index d6667748..dd6ddd69 100644 --- a/backend/src/plugins/LocateUser/utils/sendWhere.ts +++ b/backend/src/plugins/LocateUser/utils/sendWhere.ts @@ -1,22 +1,22 @@ -import { Invite, Member, TextableChannel, VoiceChannel } from "eris"; -import { getInviteLink } from "knub/dist/helpers"; -import { createOrReuseInvite } from "./createOrReuseInvite"; +import { GuildMember, Invite, TextChannel, VoiceChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { LocateUserPluginType } from "../types"; +import { getInviteLink } from "knub/dist/helpers"; import { sendErrorMessage } from "../../../pluginUtils"; +import { LocateUserPluginType } from "../types"; +import { createOrReuseInvite } from "./createOrReuseInvite"; export async function sendWhere( pluginData: GuildPluginData, - member: Member, - channel: TextableChannel, + member: GuildMember, + channel: TextChannel, prepend: string, ) { - const voice = member.voiceState.channelID - ? (pluginData.guild.channels.get(member.voiceState.channelID) as VoiceChannel) + const voice = member.voice.channelId + ? (pluginData.guild.channels.resolve(member.voice.channelId) as VoiceChannel) : null; if (voice == null) { - channel.createMessage(prepend + "That user is not in a channel"); + channel.send(prepend + "That user is not in a channel"); } else { let invite: Invite; try { @@ -25,9 +25,9 @@ export async function sendWhere( sendErrorMessage(pluginData, channel, "Cannot create an invite to that channel!"); return; } - channel.createMessage({ - content: prepend + `${member.mention} is in the following channel: \`${voice.name}\` ${getInviteLink(invite)}`, - allowedMentions: { users: true }, + channel.send({ + content: prepend + `<@${member.id}> is in the following channel: \`${voice.name}\` ${getInviteLink(invite)}`, + allowedMentions: { parse: ["users"] }, }); } } diff --git a/backend/src/plugins/Logs/LogsPlugin.ts b/backend/src/plugins/Logs/LogsPlugin.ts index 216be6d6..d4285de8 100644 --- a/backend/src/plugins/Logs/LogsPlugin.ts +++ b/backend/src/plugins/Logs/LogsPlugin.ts @@ -1,28 +1,42 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { PluginOptions } from "knub"; -import { ConfigSchema, FORMAT_NO_TIMESTAMP, LogsPluginType } from "./types"; import DefaultLogMessages from "../../data/DefaultLogMessages.json"; -import { GuildLogs } from "../../data/GuildLogs"; -import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildArchives } from "../../data/GuildArchives"; import { GuildCases } from "../../data/GuildCases"; +import { GuildLogs } from "../../data/GuildLogs"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { LogType } from "../../data/LogType"; +import { logger } from "../../logger"; +import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners"; +import { CasesPlugin } from "../Cases/CasesPlugin"; +import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import { LogsChannelCreateEvt, LogsChannelDeleteEvt, LogsChannelUpdateEvt } from "./events/LogsChannelModifyEvts"; +import { LogsGuildMemberAddEvt } from "./events/LogsGuildMemberAddEvt"; +import { LogsGuildMemberRemoveEvt } from "./events/LogsGuildMemberRemoveEvt"; +import { LogsRoleCreateEvt, LogsRoleDeleteEvt, LogsRoleUpdateEvt } from "./events/LogsRoleModifyEvts"; +import { + LogsStageInstanceCreateEvt, + LogsStageInstanceDeleteEvt, + LogsStageInstanceUpdateEvt, +} from "./events/LogsStageInstanceModifyEvts"; +import { + LogsEmojiCreateEvt, + LogsEmojiDeleteEvt, + LogsEmojiUpdateEvt, + LogsStickerCreateEvt, + LogsStickerDeleteEvt, + LogsStickerUpdateEvt, +} from "./events/LogsEmojiAndStickerModifyEvts"; +import { LogsThreadCreateEvt, LogsThreadDeleteEvt, LogsThreadUpdateEvt } from "./events/LogsThreadModifyEvts"; +import { LogsGuildMemberUpdateEvt } from "./events/LogsUserUpdateEvts"; +import { LogsVoiceStateUpdateEvt } from "./events/LogsVoiceChannelEvts"; +import { ConfigSchema, FORMAT_NO_TIMESTAMP, LogsPluginType } from "./types"; +import { getLogMessage } from "./util/getLogMessage"; +import { log } from "./util/log"; import { onMessageDelete } from "./util/onMessageDelete"; import { onMessageDeleteBulk } from "./util/onMessageDeleteBulk"; import { onMessageUpdate } from "./util/onMessageUpdate"; -import { LogsGuildMemberAddEvt } from "./events/LogsGuildMemberAddEvt"; -import { LogsGuildMemberRemoveEvt } from "./events/LogsGuildMemberRemoveEvt"; -import { LogsGuildMemberUpdateEvt } from "./events/LogsUserUpdateEvts"; -import { LogsChannelCreateEvt, LogsChannelDeleteEvt } from "./events/LogsChannelModifyEvts"; -import { LogsRoleCreateEvt, LogsRoleDeleteEvt } from "./events/LogsRoleModifyEvts"; -import { LogsVoiceJoinEvt, LogsVoiceLeaveEvt, LogsVoiceSwitchEvt } from "./events/LogsVoiceChannelEvts"; -import { log } from "./util/log"; -import { LogType } from "../../data/LogType"; -import { getLogMessage } from "./util/getLogMessage"; -import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners"; -import { disableCodeBlocks } from "../../utils"; -import { logger } from "../../logger"; -import { CasesPlugin } from "../Cases/CasesPlugin"; -import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; +import { Util } from "discord.js"; const defaultOptions: PluginOptions = { config: { @@ -64,11 +78,23 @@ export const LogsPlugin = zeppelinGuildPlugin()({ LogsGuildMemberUpdateEvt, LogsChannelCreateEvt, LogsChannelDeleteEvt, + LogsChannelUpdateEvt, LogsRoleCreateEvt, LogsRoleDeleteEvt, - LogsVoiceJoinEvt, - LogsVoiceLeaveEvt, - LogsVoiceSwitchEvt, + LogsRoleUpdateEvt, + LogsVoiceStateUpdateEvt, + LogsStageInstanceCreateEvt, + LogsStageInstanceDeleteEvt, + LogsStageInstanceUpdateEvt, + LogsThreadCreateEvt, + LogsThreadDeleteEvt, + LogsThreadUpdateEvt, + LogsEmojiCreateEvt, + LogsEmojiDeleteEvt, + LogsEmojiUpdateEvt, + LogsStickerCreateEvt, + LogsStickerDeleteEvt, + LogsStickerUpdateEvt, ], public: { @@ -121,7 +147,7 @@ export const LogsPlugin = zeppelinGuildPlugin()({ The following regex has taken longer than ${timeoutMs}ms for ${failedTimes} times and has been temporarily disabled: `.trim() + "\n```" + - disableCodeBlocks(regexSource) + + Util.escapeCodeBlock(regexSource) + "```", }); }; diff --git a/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts b/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts index dad30a6a..a6aa8c64 100644 --- a/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts +++ b/backend/src/plugins/Logs/events/LogsChannelModifyEvts.ts @@ -1,13 +1,14 @@ -import { logsEvt } from "../types"; -import { stripObjectToScalars } from "../../../utils"; import { LogType } from "../../../data/LogType"; +import { differenceToString, getScalarDifference } from "../../../utils"; +import { channelToConfigAccessibleChannel } from "../../../utils/configAccessibleObjects"; +import { logsEvt } from "../types"; export const LogsChannelCreateEvt = logsEvt({ event: "channelCreate", async listener(meta) { meta.pluginData.state.guildLogs.log(LogType.CHANNEL_CREATE, { - channel: stripObjectToScalars(meta.args.channel), + channel: channelToConfigAccessibleChannel(meta.args.channel), }); }, }); @@ -17,7 +18,26 @@ export const LogsChannelDeleteEvt = logsEvt({ async listener(meta) { meta.pluginData.state.guildLogs.log(LogType.CHANNEL_DELETE, { - channel: stripObjectToScalars(meta.args.channel), + channel: channelToConfigAccessibleChannel(meta.args.channel), }); }, }); + +export const LogsChannelUpdateEvt = logsEvt({ + event: "channelUpdate", + + async listener(meta) { + const diff = getScalarDifference(meta.args.oldChannel, meta.args.newChannel); + const differenceString = differenceToString(diff); + + meta.pluginData.state.guildLogs.log( + LogType.CHANNEL_UPDATE, + { + oldChannel: channelToConfigAccessibleChannel(meta.args.oldChannel), + newChannel: channelToConfigAccessibleChannel(meta.args.newChannel), + differenceString, + }, + meta.args.newChannel.id, + ); + }, +}); diff --git a/backend/src/plugins/Logs/events/LogsEmojiAndStickerModifyEvts.ts b/backend/src/plugins/Logs/events/LogsEmojiAndStickerModifyEvts.ts new file mode 100644 index 00000000..4f8ce433 --- /dev/null +++ b/backend/src/plugins/Logs/events/LogsEmojiAndStickerModifyEvts.ts @@ -0,0 +1,82 @@ +import { LogType } from "../../../data/LogType"; +import { differenceToString, getScalarDifference } from "../../../utils"; +import { + channelToConfigAccessibleChannel, + emojiToConfigAccessibleEmoji, + stickerToConfigAccessibleSticker, +} from "../../../utils/configAccessibleObjects"; +import { logsEvt } from "../types"; + +export const LogsEmojiCreateEvt = logsEvt({ + event: "emojiCreate", + + async listener(meta) { + meta.pluginData.state.guildLogs.log(LogType.EMOJI_CREATE, { + emoji: emojiToConfigAccessibleEmoji(meta.args.emoji), + }); + }, +}); + +export const LogsEmojiDeleteEvt = logsEvt({ + event: "emojiDelete", + + async listener(meta) { + meta.pluginData.state.guildLogs.log(LogType.EMOJI_DELETE, { + emoji: emojiToConfigAccessibleEmoji(meta.args.emoji), + }); + }, +}); + +export const LogsEmojiUpdateEvt = logsEvt({ + event: "emojiUpdate", + + async listener(meta) { + const diff = getScalarDifference(meta.args.oldEmoji, meta.args.newEmoji); + const differenceString = differenceToString(diff); + + meta.pluginData.state.guildLogs.log(LogType.EMOJI_UPDATE, { + oldEmoji: emojiToConfigAccessibleEmoji(meta.args.oldEmoji), + newEmoji: emojiToConfigAccessibleEmoji(meta.args.newEmoji), + differenceString, + }); + }, +}); + +export const LogsStickerCreateEvt = logsEvt({ + event: "stickerCreate", + + async listener(meta) { + meta.pluginData.state.guildLogs.log(LogType.STICKER_CREATE, { + sticker: stickerToConfigAccessibleSticker(meta.args.sticker), + }); + }, +}); + +export const LogsStickerDeleteEvt = logsEvt({ + event: "stickerDelete", + + async listener(meta) { + meta.pluginData.state.guildLogs.log(LogType.STICKER_DELETE, { + sticker: stickerToConfigAccessibleSticker(meta.args.sticker), + }); + }, +}); + +export const LogsStickerUpdateEvt = logsEvt({ + event: "stickerUpdate", + + async listener(meta) { + const diff = getScalarDifference(meta.args.oldSticker, meta.args.newSticker); + const differenceString = differenceToString(diff); + + meta.pluginData.state.guildLogs.log( + LogType.STICKER_UPDATE, + { + oldSticker: stickerToConfigAccessibleSticker(meta.args.oldSticker), + newSticker: stickerToConfigAccessibleSticker(meta.args.newSticker), + differenceString, + }, + meta.args.newSticker.id, + ); + }, +}); diff --git a/backend/src/plugins/Logs/events/LogsGuildBanEvts.ts b/backend/src/plugins/Logs/events/LogsGuildBanEvts.ts index 27bbbe87..580ce85e 100644 --- a/backend/src/plugins/Logs/events/LogsGuildBanEvts.ts +++ b/backend/src/plugins/Logs/events/LogsGuildBanEvts.ts @@ -1,28 +1,28 @@ -import { logsEvt } from "../types"; -import { stripObjectToScalars, UnknownUser } from "../../../utils"; +import { GuildAuditLogs } from "discord.js"; import { LogType } from "../../../data/LogType"; -import { Constants as ErisConstants } from "eris"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { safeFindRelevantAuditLogEntry } from "../../../utils/safeFindRelevantAuditLogEntry"; +import { logsEvt } from "../types"; export const LogsGuildBanAddEvt = logsEvt({ event: "guildBanAdd", async listener(meta) { const pluginData = meta.pluginData; - const user = meta.args.user; + const user = meta.args.ban.user; const relevantAuditLogEntry = await safeFindRelevantAuditLogEntry( pluginData, - ErisConstants.AuditLogActions.MEMBER_BAN_ADD, + GuildAuditLogs.Actions.MEMBER_BAN_ADD as number, user.id, ); - const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : new UnknownUser(); + const mod = relevantAuditLogEntry?.executor ?? null; pluginData.state.guildLogs.log( LogType.MEMBER_BAN, { - mod: stripObjectToScalars(mod), - user: stripObjectToScalars(user), + mod: mod ? userToConfigAccessibleUser(mod) : {}, + user: userToConfigAccessibleUser(user), }, user.id, ); @@ -34,19 +34,19 @@ export const LogsGuildBanRemoveEvt = logsEvt({ async listener(meta) { const pluginData = meta.pluginData; - const user = meta.args.user; + const user = meta.args.ban.user; const relevantAuditLogEntry = await safeFindRelevantAuditLogEntry( pluginData, - ErisConstants.AuditLogActions.MEMBER_BAN_REMOVE, + GuildAuditLogs.Actions.MEMBER_BAN_REMOVE as number, user.id, ); - const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : new UnknownUser(); + const mod = relevantAuditLogEntry?.executor ?? null; pluginData.state.guildLogs.log( LogType.MEMBER_UNBAN, { - mod: stripObjectToScalars(mod), + mod: mod ? userToConfigAccessibleUser(mod) : {}, userId: user.id, }, user.id, diff --git a/backend/src/plugins/Logs/events/LogsGuildMemberAddEvt.ts b/backend/src/plugins/Logs/events/LogsGuildMemberAddEvt.ts index b7a731f7..099c3cb3 100644 --- a/backend/src/plugins/Logs/events/LogsGuildMemberAddEvt.ts +++ b/backend/src/plugins/Logs/events/LogsGuildMemberAddEvt.ts @@ -1,9 +1,9 @@ -import { logsEvt } from "../types"; -import { stripObjectToScalars } from "../../../utils"; -import { LogType } from "../../../data/LogType"; -import moment from "moment-timezone"; import humanizeDuration from "humanize-duration"; +import moment from "moment-timezone"; +import { LogType } from "../../../data/LogType"; +import { memberToConfigAccessibleMember } from "../../../utils/configAccessibleObjects"; import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { logsEvt } from "../types"; export const LogsGuildMemberAddEvt = logsEvt({ event: "guildMemberAdd", @@ -13,14 +13,14 @@ export const LogsGuildMemberAddEvt = logsEvt({ const member = meta.args.member; const newThreshold = moment.utc().valueOf() - 1000 * 60 * 60; - const accountAge = humanizeDuration(moment.utc().valueOf() - member.createdAt, { + const accountAge = humanizeDuration(moment.utc().valueOf() - member.user.createdTimestamp, { largest: 2, round: true, }); pluginData.state.guildLogs.log(LogType.MEMBER_JOIN, { - member: stripObjectToScalars(member, ["user", "roles"]), - new: member.createdAt >= newThreshold ? " :new:" : "", + member: memberToConfigAccessibleMember(member), + new: member.user.createdTimestamp >= newThreshold ? " :new:" : "", account_age: accountAge, }); @@ -46,7 +46,7 @@ export const LogsGuildMemberAddEvt = logsEvt({ } pluginData.state.guildLogs.log(LogType.MEMBER_JOIN_WITH_PRIOR_RECORDS, { - member: stripObjectToScalars(member, ["user", "roles"]), + member: memberToConfigAccessibleMember(member), recentCaseSummary, }); } diff --git a/backend/src/plugins/Logs/events/LogsGuildMemberRemoveEvt.ts b/backend/src/plugins/Logs/events/LogsGuildMemberRemoveEvt.ts index cde1fbd8..8b18516d 100644 --- a/backend/src/plugins/Logs/events/LogsGuildMemberRemoveEvt.ts +++ b/backend/src/plugins/Logs/events/LogsGuildMemberRemoveEvt.ts @@ -1,13 +1,13 @@ -import { logsEvt } from "../types"; -import { stripObjectToScalars } from "../../../utils"; import { LogType } from "../../../data/LogType"; +import { memberToConfigAccessibleMember } from "../../../utils/configAccessibleObjects"; +import { logsEvt } from "../types"; export const LogsGuildMemberRemoveEvt = logsEvt({ event: "guildMemberRemove", async listener(meta) { meta.pluginData.state.guildLogs.log(LogType.MEMBER_LEAVE, { - member: stripObjectToScalars(meta.args.member, ["user", "roles"]), + member: memberToConfigAccessibleMember(meta.args.member), }); }, }); diff --git a/backend/src/plugins/Logs/events/LogsRoleModifyEvts.ts b/backend/src/plugins/Logs/events/LogsRoleModifyEvts.ts index a24d7929..e9ad1b07 100644 --- a/backend/src/plugins/Logs/events/LogsRoleModifyEvts.ts +++ b/backend/src/plugins/Logs/events/LogsRoleModifyEvts.ts @@ -1,23 +1,39 @@ -import { logsEvt } from "../types"; -import { stripObjectToScalars } from "../../../utils"; import { LogType } from "../../../data/LogType"; +import { differenceToString, getScalarDifference } from "../../../utils"; +import { roleToConfigAccessibleRole } from "../../../utils/configAccessibleObjects"; +import { logsEvt } from "../types"; export const LogsRoleCreateEvt = logsEvt({ - event: "guildRoleCreate", + event: "roleCreate", async listener(meta) { meta.pluginData.state.guildLogs.log(LogType.ROLE_CREATE, { - role: stripObjectToScalars(meta.args.role), + role: roleToConfigAccessibleRole(meta.args.role), }); }, }); export const LogsRoleDeleteEvt = logsEvt({ - event: "guildRoleDelete", + event: "roleDelete", async listener(meta) { meta.pluginData.state.guildLogs.log(LogType.ROLE_DELETE, { - role: stripObjectToScalars(meta.args.role), + role: roleToConfigAccessibleRole(meta.args.role), + }); + }, +}); + +export const LogsRoleUpdateEvt = logsEvt({ + event: "roleUpdate", + + async listener(meta) { + const diff = getScalarDifference(meta.args.oldRole, meta.args.newRole); + const differenceString = differenceToString(diff); + + meta.pluginData.state.guildLogs.log(LogType.ROLE_UPDATE, { + newRole: roleToConfigAccessibleRole(meta.args.newRole), + oldRole: roleToConfigAccessibleRole(meta.args.oldRole), + differenceString, }); }, }); diff --git a/backend/src/plugins/Logs/events/LogsStageInstanceModifyEvts.ts b/backend/src/plugins/Logs/events/LogsStageInstanceModifyEvts.ts new file mode 100644 index 00000000..b2cb0989 --- /dev/null +++ b/backend/src/plugins/Logs/events/LogsStageInstanceModifyEvts.ts @@ -0,0 +1,54 @@ +import { LogType } from "../../../data/LogType"; +import { differenceToString, getScalarDifference } from "../../../utils"; +import { channelToConfigAccessibleChannel, stageToConfigAccessibleStage } from "../../../utils/configAccessibleObjects"; +import { logsEvt } from "../types"; + +export const LogsStageInstanceCreateEvt = logsEvt({ + event: "stageInstanceCreate", + + async listener(meta) { + const stageChannel = + meta.args.stageInstance.channel ?? + (await meta.pluginData.guild.channels.fetch(meta.args.stageInstance.channelId))!; + + meta.pluginData.state.guildLogs.log(LogType.STAGE_INSTANCE_CREATE, { + stageInstance: stageToConfigAccessibleStage(meta.args.stageInstance), + stageChannel: channelToConfigAccessibleChannel(stageChannel), + }); + }, +}); + +export const LogsStageInstanceDeleteEvt = logsEvt({ + event: "stageInstanceDelete", + + async listener(meta) { + const stageChannel = + meta.args.stageInstance.channel ?? + (await meta.pluginData.guild.channels.fetch(meta.args.stageInstance.channelId))!; + + meta.pluginData.state.guildLogs.log(LogType.STAGE_INSTANCE_DELETE, { + stageInstance: stageToConfigAccessibleStage(meta.args.stageInstance), + stageChannel: channelToConfigAccessibleChannel(stageChannel), + }); + }, +}); + +export const LogsStageInstanceUpdateEvt = logsEvt({ + event: "stageInstanceUpdate", + + async listener(meta) { + const stageChannel = + meta.args.newStageInstance.channel ?? + (await meta.pluginData.guild.channels.fetch(meta.args.newStageInstance.channelId))!; + + const diff = getScalarDifference(meta.args.oldStageInstance, meta.args.newStageInstance); + const differenceString = differenceToString(diff); + + meta.pluginData.state.guildLogs.log(LogType.STAGE_INSTANCE_UPDATE, { + oldStageInstance: stageToConfigAccessibleStage(meta.args.oldStageInstance), + newStageInstance: stageToConfigAccessibleStage(meta.args.newStageInstance), + stageChannel: channelToConfigAccessibleChannel(stageChannel), + differenceString, + }); + }, +}); diff --git a/backend/src/plugins/Logs/events/LogsThreadModifyEvts.ts b/backend/src/plugins/Logs/events/LogsThreadModifyEvts.ts new file mode 100644 index 00000000..f20c15e4 --- /dev/null +++ b/backend/src/plugins/Logs/events/LogsThreadModifyEvts.ts @@ -0,0 +1,43 @@ +import { LogType } from "../../../data/LogType"; +import { differenceToString, getScalarDifference } from "../../../utils"; +import { channelToConfigAccessibleChannel } from "../../../utils/configAccessibleObjects"; +import { logsEvt } from "../types"; + +export const LogsThreadCreateEvt = logsEvt({ + event: "threadCreate", + + async listener(meta) { + meta.pluginData.state.guildLogs.log(LogType.THREAD_CREATE, { + thread: channelToConfigAccessibleChannel(meta.args.thread), + }); + }, +}); + +export const LogsThreadDeleteEvt = logsEvt({ + event: "threadDelete", + + async listener(meta) { + meta.pluginData.state.guildLogs.log(LogType.THREAD_DELETE, { + thread: channelToConfigAccessibleChannel(meta.args.thread), + }); + }, +}); + +export const LogsThreadUpdateEvt = logsEvt({ + event: "threadUpdate", + + async listener(meta) { + const diff = getScalarDifference(meta.args.oldThread, meta.args.newThread, ["messageCount", "archiveTimestamp"]); + const differenceString = differenceToString(diff); + + meta.pluginData.state.guildLogs.log( + LogType.THREAD_UPDATE, + { + oldThread: channelToConfigAccessibleChannel(meta.args.oldThread), + newThread: channelToConfigAccessibleChannel(meta.args.newThread), + differenceString, + }, + meta.args.newThread.id, + ); + }, +}); diff --git a/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts b/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts index 46228646..a5f7e9c7 100644 --- a/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts +++ b/backend/src/plugins/Logs/events/LogsUserUpdateEvts.ts @@ -1,10 +1,10 @@ -import { logsEvt } from "../types"; -import { stripObjectToScalars, UnknownUser } from "../../../utils"; -import { Constants as ErisConstants } from "eris"; -import { LogType } from "../../../data/LogType"; -import isEqual from "lodash.isequal"; +import { GuildAuditLogs } from "discord.js"; import diff from "lodash.difference"; +import isEqual from "lodash.isequal"; +import { memberToConfigAccessibleMember, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { LogType } from "../../../data/LogType"; import { safeFindRelevantAuditLogEntry } from "../../../utils/safeFindRelevantAuditLogEntry"; +import { logsEvt } from "../types"; export const LogsGuildMemberUpdateEvt = logsEvt({ event: "guildMemberUpdate", @@ -12,23 +12,23 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ async listener(meta) { const pluginData = meta.pluginData; const oldMember = meta.args.oldMember; - const member = meta.args.member; + const member = meta.args.newMember; if (!oldMember) return; - const logMember = stripObjectToScalars(member, ["user", "roles"]); + const logMember = memberToConfigAccessibleMember(member); - if (member.nick !== oldMember.nick) { + if (member.nickname !== oldMember.nickname) { pluginData.state.guildLogs.log(LogType.MEMBER_NICK_CHANGE, { member: logMember, - oldNick: oldMember.nick != null ? oldMember.nick : "", - newNick: member.nick != null ? member.nick : "", + oldNick: oldMember.nickname != null ? oldMember.nickname : "", + newNick: member.nickname != null ? member.nickname : "", }); } if (!isEqual(oldMember.roles, member.roles)) { - const addedRoles = diff(member.roles, oldMember.roles); - const removedRoles = diff(oldMember.roles, member.roles); + const addedRoles = diff([...member.roles.cache.keys()], [...oldMember.roles.cache.keys()]); + const removedRoles = diff([...oldMember.roles.cache.keys()], [...member.roles.cache.keys()]); let skip = false; if ( @@ -49,10 +49,10 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ if (!skip) { const relevantAuditLogEntry = await safeFindRelevantAuditLogEntry( pluginData, - ErisConstants.AuditLogActions.MEMBER_ROLE_UPDATE, + GuildAuditLogs.Actions.MEMBER_ROLE_UPDATE as number, member.id, ); - const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : new UnknownUser(); + const mod = relevantAuditLogEntry?.executor ?? null; if (addedRoles.length && removedRoles.length) { // Roles added *and* removed @@ -61,14 +61,14 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ { member: logMember, addedRoles: addedRoles - .map(roleId => pluginData.guild.roles.get(roleId) || { id: roleId, name: `Unknown (${roleId})` }) + .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) .map(r => r.name) .join(", "), removedRoles: removedRoles - .map(roleId => pluginData.guild.roles.get(roleId) || { id: roleId, name: `Unknown (${roleId})` }) + .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) .map(r => r.name) .join(", "), - mod: stripObjectToScalars(mod), + mod: mod ? userToConfigAccessibleUser(mod) : {}, }, member.id, ); @@ -79,10 +79,10 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ { member: logMember, roles: addedRoles - .map(roleId => pluginData.guild.roles.get(roleId) || { id: roleId, name: `Unknown (${roleId})` }) + .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) .map(r => r.name) .join(", "), - mod: stripObjectToScalars(mod), + mod: mod ? userToConfigAccessibleUser(mod) : {}, }, member.id, ); @@ -93,10 +93,10 @@ export const LogsGuildMemberUpdateEvt = logsEvt({ { member: logMember, roles: removedRoles - .map(roleId => pluginData.guild.roles.get(roleId) || { id: roleId, name: `Unknown (${roleId})` }) + .map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` }) .map(r => r.name) .join(", "), - mod: stripObjectToScalars(mod), + mod: mod ? userToConfigAccessibleUser(mod) : {}, }, member.id, ); diff --git a/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts b/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts index a1899d5c..935c7847 100644 --- a/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts +++ b/backend/src/plugins/Logs/events/LogsVoiceChannelEvts.ts @@ -1,37 +1,36 @@ -import { logsEvt } from "../types"; -import { stripObjectToScalars } from "../../../utils"; +import { + channelToConfigAccessibleChannel, + memberToConfigAccessibleMember, +} from "../../../utils/configAccessibleObjects"; import { LogType } from "../../../data/LogType"; +import { logsEvt } from "../types"; -export const LogsVoiceJoinEvt = logsEvt({ - event: "voiceChannelJoin", +export const LogsVoiceStateUpdateEvt = logsEvt({ + event: "voiceStateUpdate", async listener(meta) { - meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_JOIN, { - member: stripObjectToScalars(meta.args.member, ["user", "roles"]), - channel: stripObjectToScalars(meta.args.newChannel), - }); - }, -}); - -export const LogsVoiceLeaveEvt = logsEvt({ - event: "voiceChannelLeave", - - async listener(meta) { - meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_LEAVE, { - member: stripObjectToScalars(meta.args.member, ["user", "roles"]), - channel: stripObjectToScalars(meta.args.oldChannel), - }); - }, -}); - -export const LogsVoiceSwitchEvt = logsEvt({ - event: "voiceChannelSwitch", - - async listener(meta) { - meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_MOVE, { - member: stripObjectToScalars(meta.args.member, ["user", "roles"]), - oldChannel: stripObjectToScalars(meta.args.oldChannel), - newChannel: stripObjectToScalars(meta.args.newChannel), - }); + const oldChannel = meta.args.oldState.channel; + const newChannel = meta.args.newState.channel; + const member = meta.args.newState.member ?? meta.args.oldState.member!; + + if (!newChannel && oldChannel) { + // Leave evt + meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_LEAVE, { + member: memberToConfigAccessibleMember(member), + channel: channelToConfigAccessibleChannel(oldChannel!), + }); + } else if (!oldChannel && newChannel) { + // Join Evt + meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_JOIN, { + member: memberToConfigAccessibleMember(member), + channel: channelToConfigAccessibleChannel(newChannel), + }); + } else { + meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_MOVE, { + member: memberToConfigAccessibleMember(member), + oldChannel: channelToConfigAccessibleChannel(oldChannel!), + newChannel: channelToConfigAccessibleChannel(newChannel!), + }); + } }, }); diff --git a/backend/src/plugins/Logs/types.ts b/backend/src/plugins/Logs/types.ts index bdb96a4d..56178d6b 100644 --- a/backend/src/plugins/Logs/types.ts +++ b/backend/src/plugins/Logs/types.ts @@ -1,12 +1,12 @@ import * as t from "io-ts"; import { BasePluginType, typedGuildEventListener } from "knub"; -import { TRegex } from "../../validatorUtils"; -import { GuildLogs } from "../../data/GuildLogs"; -import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildArchives } from "../../data/GuildArchives"; import { GuildCases } from "../../data/GuildCases"; -import { tMessageContent, tNullable } from "../../utils"; +import { GuildLogs } from "../../data/GuildLogs"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { RegExpRunner } from "../../RegExpRunner"; +import { tMessageContent, tNullable } from "../../utils"; +import { TRegex } from "../../validatorUtils"; export const tLogFormats = t.record(t.string, t.union([t.string, tMessageContent])); export type TLogFormats = t.TypeOf; diff --git a/backend/src/plugins/Logs/util/getLogMessage.ts b/backend/src/plugins/Logs/util/getLogMessage.ts index d107b7ef..7ffd0a17 100644 --- a/backend/src/plugins/Logs/util/getLogMessage.ts +++ b/backend/src/plugins/Logs/util/getLogMessage.ts @@ -1,27 +1,26 @@ +import { MessageOptions } from "discord.js"; import { GuildPluginData } from "knub"; -import { FORMAT_NO_TIMESTAMP, LogsPluginType, TLogChannel, TLogFormats } from "../types"; +import { SavedMessage } from "../../../data/entities/SavedMessage"; import { LogType } from "../../../data/LogType"; +import { logger } from "../../../logger"; +import { renderTemplate, TemplateParseError } from "../../../templateFormatter"; import { + messageSummary, + renderRecursively, + resolveMember, + verboseChannelMention, verboseUserMention, verboseUserName, - verboseChannelMention, - messageSummary, - resolveMember, - renderRecursively, } from "../../../utils"; -import { SavedMessage } from "../../../data/entities/SavedMessage"; -import { renderTemplate, TemplateParseError } from "../../../templateFormatter"; -import { logger } from "../../../logger"; -import moment from "moment-timezone"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; -import { MessageContent } from "eris"; +import { FORMAT_NO_TIMESTAMP, LogsPluginType, TLogChannel } from "../types"; export async function getLogMessage( pluginData: GuildPluginData, type: LogType, data: any, opts?: Pick, -): Promise { +): Promise { const config = pluginData.config.get(); const format = opts?.format?.[LogType[type]] || config.format[LogType[type]] || ""; if (format === "" || format == null) return null; diff --git a/backend/src/plugins/Logs/util/log.ts b/backend/src/plugins/Logs/util/log.ts index 6072284e..b8e6eed0 100644 --- a/backend/src/plugins/Logs/util/log.ts +++ b/backend/src/plugins/Logs/util/log.ts @@ -1,11 +1,11 @@ +import { MessageMentionTypes, Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { LogsPluginType, TLogChannelMap } from "../types"; -import { LogType } from "../../../data/LogType"; -import { TextChannel } from "eris"; -import { createChunkedMessage, get, noop } from "../../../utils"; -import { getLogMessage } from "./getLogMessage"; -import { allowTimeout } from "../../../RegExpRunner"; import { SavedMessage } from "../../../data/entities/SavedMessage"; +import { LogType } from "../../../data/LogType"; +import { allowTimeout } from "../../../RegExpRunner"; +import { createChunkedMessage, get, noop } from "../../../utils"; +import { LogsPluginType, TLogChannelMap } from "../types"; +import { getLogMessage } from "./getLogMessage"; const excludedUserProps = ["user", "member", "mod"]; const excludedRoleProps = ["message.member.roles", "member.roles"]; @@ -19,7 +19,7 @@ export async function log(pluginData: GuildPluginData, type: Log const typeStr = LogType[type]; logChannelLoop: for (const [channelId, opts] of Object.entries(logChannels)) { - const channel = pluginData.guild.channels.get(channelId); + const channel = pluginData.guild.channels.cache.get(channelId as Snowflake); if (!channel || !(channel instanceof TextChannel)) continue; if ((opts.include && opts.include.includes(typeStr)) || (opts.exclude && !opts.exclude.includes(typeStr))) { @@ -45,9 +45,9 @@ export async function log(pluginData: GuildPluginData, type: Log if (opts.excluded_roles) { for (const value of Object.values(data || {})) { if (value instanceof SavedMessage) { - const member = pluginData.guild.members.get(value.user_id); - for (const role of member?.roles || []) { - if (opts.excluded_roles.includes(role)) { + const member = pluginData.guild.members.cache.get(value.user_id as Snowflake); + for (const role of member?.roles.cache || []) { + if (opts.excluded_roles.includes(role[0])) { continue logChannelLoop; } } @@ -94,7 +94,7 @@ export async function log(pluginData: GuildPluginData, type: Log type === LogType.CENSOR || type === LogType.CLEAN ) { - if (data.channel.parentID && opts.excluded_categories.includes(data.channel.parentID)) { + if (data.channel.parentId && opts.excluded_categories.includes(data.channel.parentId)) { continue logChannelLoop; } } @@ -128,7 +128,7 @@ export async function log(pluginData: GuildPluginData, type: Log if (message) { // For non-string log messages (i.e. embeds) batching or chunking is not possible, so send them immediately if (typeof message !== "string") { - await channel.createMessage(message).catch(noop); + await channel.send(message).catch(noop); return; } @@ -136,6 +136,7 @@ export async function log(pluginData: GuildPluginData, type: Log const batched = opts.batched ?? true; const batchTime = opts.batch_time ?? 1000; const cfg = pluginData.config.get(); + const parse: MessageMentionTypes[] = cfg.allow_user_mentions ? ["users"] : []; if (batched) { // If we're batching log messages, gather all log messages within the set batch_time into a single message @@ -144,14 +145,14 @@ export async function log(pluginData: GuildPluginData, type: Log setTimeout(async () => { const batchedMessage = pluginData.state.batches.get(channel.id)!.join("\n"); pluginData.state.batches.delete(channel.id); - createChunkedMessage(channel, batchedMessage, { users: cfg.allow_user_mentions }).catch(noop); + createChunkedMessage(channel, batchedMessage, { parse }).catch(noop); }, batchTime); } pluginData.state.batches.get(channel.id)!.push(message); } else { // If we're not batching log messages, just send them immediately - await createChunkedMessage(channel, message, { users: cfg.allow_user_mentions }).catch(noop); + await createChunkedMessage(channel, message, { parse }).catch(noop); } } } diff --git a/backend/src/plugins/Logs/util/onMessageDelete.ts b/backend/src/plugins/Logs/util/onMessageDelete.ts index 3319f4f2..f1f64893 100644 --- a/backend/src/plugins/Logs/util/onMessageDelete.ts +++ b/backend/src/plugins/Logs/util/onMessageDelete.ts @@ -1,20 +1,21 @@ -import { SavedMessage } from "../../../data/entities/SavedMessage"; -import { Attachment } from "eris"; -import { useMediaUrls, stripObjectToScalars, resolveUser } from "../../../utils"; -import { LogType } from "../../../data/LogType"; -import moment from "moment-timezone"; +import { MessageAttachment, Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; -import { FORMAT_NO_TIMESTAMP, LogsPluginType } from "../types"; +import moment from "moment-timezone"; +import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { SavedMessage } from "../../../data/entities/SavedMessage"; +import { LogType } from "../../../data/LogType"; +import { resolveUser, useMediaUrls } from "../../../utils"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { FORMAT_NO_TIMESTAMP, LogsPluginType } from "../types"; export async function onMessageDelete(pluginData: GuildPluginData, savedMessage: SavedMessage) { const user = await resolveUser(pluginData.client, savedMessage.user_id); - const channel = pluginData.guild.channels.get(savedMessage.channel_id); + const channel = pluginData.guild.channels.resolve(savedMessage.channel_id as Snowflake)!; if (user) { // Replace attachment URLs with media URLs if (savedMessage.data.attachments) { - for (const attachment of savedMessage.data.attachments as Attachment[]) { + for (const attachment of savedMessage.data.attachments as MessageAttachment[]) { attachment.url = useMediaUrls(attachment.url); } } @@ -27,8 +28,8 @@ export async function onMessageDelete(pluginData: GuildPluginData, savedMessages: SavedMessage[]) { - const channel = pluginData.guild.channels.get(savedMessages[0].channel_id); + const channel = pluginData.guild.channels.cache.get(savedMessages[0].channel_id as Snowflake); const archiveId = await pluginData.state.archives.createFromSavedMessages(savedMessages, pluginData.guild); const archiveUrl = pluginData.state.archives.getUrl(getBaseUrl(pluginData), archiveId); const authorIds = Array.from(new Set(savedMessages.map(item => `\`${item.user_id}\``))).join(", "); diff --git a/backend/src/plugins/Logs/util/onMessageUpdate.ts b/backend/src/plugins/Logs/util/onMessageUpdate.ts index 91eb7abb..1d0efd1a 100644 --- a/backend/src/plugins/Logs/util/onMessageUpdate.ts +++ b/backend/src/plugins/Logs/util/onMessageUpdate.ts @@ -1,10 +1,11 @@ +import { MessageEmbed, Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; -import { LogsPluginType } from "../types"; -import { SavedMessage } from "../../../data/entities/SavedMessage"; -import { Embed } from "eris"; -import { LogType } from "../../../data/LogType"; -import { stripObjectToScalars, resolveUser } from "../../../utils"; import cloneDeep from "lodash.clonedeep"; +import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { SavedMessage } from "../../../data/entities/SavedMessage"; +import { LogType } from "../../../data/LogType"; +import { resolveUser } from "../../../utils"; +import { LogsPluginType } from "../types"; export async function onMessageUpdate( pluginData: GuildPluginData, @@ -14,13 +15,13 @@ export async function onMessageUpdate( // To log a message update, either the message content or a rich embed has to change let logUpdate = false; - const oldEmbedsToCompare = ((oldSavedMessage.data.embeds || []) as Embed[]) + const oldEmbedsToCompare = ((oldSavedMessage.data.embeds || []) as MessageEmbed[]) .map(e => cloneDeep(e)) - .filter(e => (e as Embed).type === "rich"); + .filter(e => (e as MessageEmbed).type === "rich"); - const newEmbedsToCompare = ((savedMessage.data.embeds || []) as Embed[]) + const newEmbedsToCompare = ((savedMessage.data.embeds || []) as MessageEmbed[]) .map(e => cloneDeep(e)) - .filter(e => (e as Embed).type === "rich"); + .filter(e => (e as MessageEmbed).type === "rich"); for (const embed of [...oldEmbedsToCompare, ...newEmbedsToCompare]) { if (embed.thumbnail) { @@ -47,11 +48,11 @@ export async function onMessageUpdate( } const user = await resolveUser(pluginData.client, savedMessage.user_id); - const channel = pluginData.guild.channels.get(savedMessage.channel_id); + const channel = pluginData.guild.channels.resolve(savedMessage.channel_id as Snowflake)!; pluginData.state.guildLogs.log(LogType.MESSAGE_EDIT, { - user: stripObjectToScalars(user), - channel: stripObjectToScalars(channel), + user: userToConfigAccessibleUser(user), + channel: channelToConfigAccessibleChannel(channel), before: oldSavedMessage, after: savedMessage, }); diff --git a/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts index 955663cb..eb529ffa 100644 --- a/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts +++ b/backend/src/plugins/MessageSaver/MessageSaverPlugin.ts @@ -1,10 +1,10 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { ConfigSchema, MessageSaverPluginType } from "./types"; -import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { PluginOptions } from "knub"; -import { MessageCreateEvt, MessageDeleteBulkEvt, MessageDeleteEvt, MessageUpdateEvt } from "./events/SaveMessagesEvts"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { SaveMessagesToDBCmd } from "./commands/SaveMessagesToDB"; import { SavePinsToDBCmd } from "./commands/SavePinsToDB"; +import { MessageCreateEvt, MessageDeleteBulkEvt, MessageDeleteEvt, MessageUpdateEvt } from "./events/SaveMessagesEvts"; +import { ConfigSchema, MessageSaverPluginType } from "./types"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts b/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts index 114da844..3d2456e8 100644 --- a/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts +++ b/backend/src/plugins/MessageSaver/commands/SaveMessagesToDB.ts @@ -1,7 +1,7 @@ -import { messageSaverCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { saveMessagesToDB } from "../saveMessagesToDB"; import { sendSuccessMessage } from "../../../pluginUtils"; +import { saveMessagesToDB } from "../saveMessagesToDB"; +import { messageSaverCmd } from "../types"; export const SaveMessagesToDBCmd = messageSaverCmd({ trigger: "save_messages_to_db", @@ -14,7 +14,7 @@ export const SaveMessagesToDBCmd = messageSaverCmd({ }, async run({ message: msg, args, pluginData }) { - await msg.channel.createMessage("Saving specified messages..."); + await msg.channel.send("Saving specified messages..."); const { savedCount, failed } = await saveMessagesToDB(pluginData, args.channel, args.ids.trim().split(" ")); if (failed.length) { diff --git a/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts b/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts index 2ecbf8e0..e81c73fe 100644 --- a/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts +++ b/backend/src/plugins/MessageSaver/commands/SavePinsToDB.ts @@ -1,7 +1,7 @@ -import { messageSaverCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { saveMessagesToDB } from "../saveMessagesToDB"; import { sendSuccessMessage } from "../../../pluginUtils"; +import { saveMessagesToDB } from "../saveMessagesToDB"; +import { messageSaverCmd } from "../types"; export const SavePinsToDBCmd = messageSaverCmd({ trigger: "save_pins_to_db", @@ -13,14 +13,10 @@ export const SavePinsToDBCmd = messageSaverCmd({ }, async run({ message: msg, args, pluginData }) { - await msg.channel.createMessage(`Saving pins from <#${args.channel.id}>...`); + await msg.channel.send(`Saving pins from <#${args.channel.id}>...`); - const pins = await args.channel.getPins(); - const { savedCount, failed } = await saveMessagesToDB( - pluginData, - args.channel, - pins.map(m => m.id), - ); + const pins = await args.channel.messages.fetchPinned(); + const { savedCount, failed } = await saveMessagesToDB(pluginData, args.channel, [...pins.keys()]); if (failed.length) { sendSuccessMessage( diff --git a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts index 00d83fcd..97f4fe6d 100644 --- a/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts +++ b/backend/src/plugins/MessageSaver/events/SaveMessagesEvts.ts @@ -1,5 +1,5 @@ +import { Message } from "discord.js"; import { messageSaverEvt } from "../types"; -import { Message } from "eris"; export const MessageCreateEvt = messageSaverEvt({ event: "messageCreate", @@ -8,7 +8,7 @@ export const MessageCreateEvt = messageSaverEvt({ async listener(meta) { // Only save regular chat messages - if (meta.args.message.type !== 0) { + if (meta.args.message.type !== "DEFAULT" && meta.args.message.type !== "REPLY") { return; } @@ -22,11 +22,11 @@ export const MessageUpdateEvt = messageSaverEvt({ allowSelf: true, async listener(meta) { - if (meta.args.message.type !== 0) { + if (meta.args.newMessage.type !== "DEFAULT" && meta.args.newMessage.type !== "REPLY") { return; } - await meta.pluginData.state.savedMessages.saveEditFromMsg(meta.args.message); + await meta.pluginData.state.savedMessages.saveEditFromMsg(meta.args.newMessage as Message); }, }); @@ -37,7 +37,7 @@ export const MessageDeleteEvt = messageSaverEvt({ async listener(meta) { const msg = meta.args.message as Message; - if (msg.type != null && msg.type !== 0) { + if (msg.type != null && meta.args.message.type !== "DEFAULT" && meta.args.message.type !== "REPLY") { return; } diff --git a/backend/src/plugins/MessageSaver/saveMessagesToDB.ts b/backend/src/plugins/MessageSaver/saveMessagesToDB.ts index 13217074..db7e9b9d 100644 --- a/backend/src/plugins/MessageSaver/saveMessagesToDB.ts +++ b/backend/src/plugins/MessageSaver/saveMessagesToDB.ts @@ -1,10 +1,10 @@ -import { MessageSaverPluginType } from "./types"; +import { Message, Snowflake, TextChannel, ThreadChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { Message, TextChannel } from "eris"; +import { MessageSaverPluginType } from "./types"; export async function saveMessagesToDB( pluginData: GuildPluginData, - channel: TextChannel, + channel: TextChannel | ThreadChannel, ids: string[], ) { const failed: string[] = []; @@ -15,7 +15,7 @@ export async function saveMessagesToDB( let thisMsg: Message; try { - thisMsg = await channel.getMessage(id); + thisMsg = await channel.messages.fetch(id as Snowflake); if (!thisMsg) { failed.push(id); diff --git a/backend/src/plugins/ModActions/ModActionsPlugin.ts b/backend/src/plugins/ModActions/ModActionsPlugin.ts index f78f7c37..d6c19a5a 100644 --- a/backend/src/plugins/ModActions/ModActionsPlugin.ts +++ b/backend/src/plugins/ModActions/ModActionsPlugin.ts @@ -1,50 +1,50 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { CasesPlugin } from "../Cases/CasesPlugin"; -import { MutesPlugin } from "../Mutes/MutesPlugin"; -import { BanOptions, ConfigSchema, KickOptions, ModActionsPluginType, WarnOptions } from "./types"; -import { CreateBanCaseOnManualBanEvt } from "./events/CreateBanCaseOnManualBanEvt"; -import { CreateUnbanCaseOnManualUnbanEvt } from "./events/CreateUnbanCaseOnManualUnbanEvt"; -import { CreateKickCaseOnManualKickEvt } from "./events/CreateKickCaseOnManualKickEvt"; -import { UpdateCmd } from "./commands/UpdateCmd"; -import { NoteCmd } from "./commands/NoteCmd"; -import { WarnCmd } from "./commands/WarnCmd"; -import { MuteCmd } from "./commands/MuteCmd"; -import { PostAlertOnMemberJoinEvt } from "./events/PostAlertOnMemberJoinEvt"; -import { ForcemuteCmd } from "./commands/ForcemuteCmd"; -import { UnmuteCmd } from "./commands/UnmuteCmd"; -import { KickCmd } from "./commands/KickCmd"; -import { SoftbanCmd } from "./commands/SoftbanCommand"; -import { BanCmd } from "./commands/BanCmd"; -import { UnbanCmd } from "./commands/UnbanCmd"; -import { ForcebanCmd } from "./commands/ForcebanCmd"; -import { MassunbanCmd } from "./commands/MassUnbanCmd"; -import { MassbanCmd } from "./commands/MassBanCmd"; -import { AddCaseCmd } from "./commands/AddCaseCmd"; -import { CaseCmd } from "./commands/CaseCmd"; -import { CasesUserCmd } from "./commands/CasesUserCmd"; -import { CasesModCmd } from "./commands/CasesModCmd"; -import { HideCaseCmd } from "./commands/HideCaseCmd"; -import { UnhideCaseCmd } from "./commands/UnhideCaseCmd"; -import { GuildMutes } from "../../data/GuildMutes"; +import { GuildMember, Message } from "discord.js"; +import { EventEmitter } from "events"; import { GuildCases } from "../../data/GuildCases"; import { GuildLogs } from "../../data/GuildLogs"; -import { ForceUnmuteCmd } from "./commands/ForceunmuteCmd"; -import { warnMember } from "./functions/warnMember"; -import { Member, Message } from "eris"; -import { kickMember } from "./functions/kickMember"; -import { banUserId } from "./functions/banUserId"; -import { MassmuteCmd } from "./commands/MassmuteCmd"; -import { MINUTES, trimPluginDescription } from "../../utils"; -import { DeleteCaseCmd } from "./commands/DeleteCaseCmd"; -import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; +import { GuildMutes } from "../../data/GuildMutes"; import { GuildTempbans } from "../../data/GuildTempbans"; -import { outdatedTempbansLoop } from "./functions/outdatedTempbansLoop"; -import { EventEmitter } from "events"; import { mapToPublicFn } from "../../pluginUtils"; -import { onModActionsEvent } from "./functions/onModActionsEvent"; -import { offModActionsEvent } from "./functions/offModActionsEvent"; -import { updateCase } from "./functions/updateCase"; import { Queue } from "../../Queue"; +import { MINUTES, trimPluginDescription } from "../../utils"; +import { CasesPlugin } from "../Cases/CasesPlugin"; +import { MutesPlugin } from "../Mutes/MutesPlugin"; +import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import { AddCaseCmd } from "./commands/AddCaseCmd"; +import { BanCmd } from "./commands/BanCmd"; +import { CaseCmd } from "./commands/CaseCmd"; +import { CasesModCmd } from "./commands/CasesModCmd"; +import { CasesUserCmd } from "./commands/CasesUserCmd"; +import { DeleteCaseCmd } from "./commands/DeleteCaseCmd"; +import { ForcebanCmd } from "./commands/ForcebanCmd"; +import { ForcemuteCmd } from "./commands/ForcemuteCmd"; +import { ForceUnmuteCmd } from "./commands/ForceunmuteCmd"; +import { HideCaseCmd } from "./commands/HideCaseCmd"; +import { KickCmd } from "./commands/KickCmd"; +import { MassbanCmd } from "./commands/MassBanCmd"; +import { MassmuteCmd } from "./commands/MassmuteCmd"; +import { MassunbanCmd } from "./commands/MassUnbanCmd"; +import { MuteCmd } from "./commands/MuteCmd"; +import { NoteCmd } from "./commands/NoteCmd"; +import { SoftbanCmd } from "./commands/SoftbanCommand"; +import { UnbanCmd } from "./commands/UnbanCmd"; +import { UnhideCaseCmd } from "./commands/UnhideCaseCmd"; +import { UnmuteCmd } from "./commands/UnmuteCmd"; +import { UpdateCmd } from "./commands/UpdateCmd"; +import { WarnCmd } from "./commands/WarnCmd"; +import { CreateBanCaseOnManualBanEvt } from "./events/CreateBanCaseOnManualBanEvt"; +import { CreateKickCaseOnManualKickEvt } from "./events/CreateKickCaseOnManualKickEvt"; +import { CreateUnbanCaseOnManualUnbanEvt } from "./events/CreateUnbanCaseOnManualUnbanEvt"; +import { PostAlertOnMemberJoinEvt } from "./events/PostAlertOnMemberJoinEvt"; +import { banUserId } from "./functions/banUserId"; +import { kickMember } from "./functions/kickMember"; +import { offModActionsEvent } from "./functions/offModActionsEvent"; +import { onModActionsEvent } from "./functions/onModActionsEvent"; +import { outdatedTempbansLoop } from "./functions/outdatedTempbansLoop"; +import { updateCase } from "./functions/updateCase"; +import { warnMember } from "./functions/warnMember"; +import { BanOptions, ConfigSchema, KickOptions, ModActionsPluginType, WarnOptions } from "./types"; const defaultOptions = { config: { @@ -158,13 +158,13 @@ export const ModActionsPlugin = zeppelinGuildPlugin()({ public: { warnMember(pluginData) { - return (member: Member, reason: string, warnOptions?: WarnOptions) => { + return (member: GuildMember, reason: string, warnOptions?: WarnOptions) => { warnMember(pluginData, member, reason, warnOptions); }; }, kickMember(pluginData) { - return (member: Member, reason: string, kickOptions?: KickOptions) => { + return (member: GuildMember, reason: string, kickOptions?: KickOptions) => { kickMember(pluginData, member, reason, kickOptions); }; }, diff --git a/backend/src/plugins/ModActions/commands/AddCaseCmd.ts b/backend/src/plugins/ModActions/commands/AddCaseCmd.ts index 6e0caf16..dd968c76 100644 --- a/backend/src/plugins/ModActions/commands/AddCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/AddCaseCmd.ts @@ -1,12 +1,13 @@ -import { modActionsCmd } from "../types"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canActOn, sendErrorMessage, hasPermission, sendSuccessMessage } from "../../../pluginUtils"; -import { resolveUser, resolveMember, stripObjectToScalars } from "../../../utils"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; import { CaseTypes } from "../../../data/CaseTypes"; -import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; import { Case } from "../../../data/entities/Case"; import { LogType } from "../../../data/LogType"; +import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; +import { canActOn, hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { resolveMember, resolveUser } from "../../../utils"; +import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; +import { modActionsCmd } from "../types"; const opts = { mod: ct.member({ option: true }), @@ -59,7 +60,7 @@ export const AddCaseCmd = modActionsCmd({ return; } - const reason = formatReasonWithAttachments(args.reason, msg.attachments); + const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]); // Create the case const casesPlugin = pluginData.getPlugin(CasesPlugin); @@ -72,18 +73,14 @@ export const AddCaseCmd = modActionsCmd({ }); if (user) { - sendSuccessMessage( - pluginData, - msg.channel, - `Case #${theCase.case_number} created for **${user.username}#${user.discriminator}**`, - ); + sendSuccessMessage(pluginData, msg.channel, `Case #${theCase.case_number} created for **${user.tag}**`); } else { sendSuccessMessage(pluginData, msg.channel, `Case #${theCase.case_number} created`); } // Log the action pluginData.state.serverLogs.log(LogType.CASE_CREATE, { - mod: stripObjectToScalars(mod.user), + mod: userToConfigAccessibleUser(mod.user), userId: user.id, caseNum: theCase.case_number, caseType: type.toUpperCase(), diff --git a/backend/src/plugins/ModActions/commands/BanCmd.ts b/backend/src/plugins/ModActions/commands/BanCmd.ts index 71f61310..6437a5e9 100644 --- a/backend/src/plugins/ModActions/commands/BanCmd.ts +++ b/backend/src/plugins/ModActions/commands/BanCmd.ts @@ -1,17 +1,19 @@ -import { modActionsCmd, IgnoredEventType } from "../types"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canActOn, sendErrorMessage, hasPermission, sendSuccessMessage } from "../../../pluginUtils"; -import { resolveUser, resolveMember, stripObjectToScalars, noop } from "../../../utils"; -import { isBanned } from "../functions/isBanned"; -import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { banUserId } from "../functions/banUserId"; -import { getMemberLevel, waitForReaction } from "knub/dist/helpers"; import humanizeDuration from "humanize-duration"; -import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; +import { getMemberLevel } from "knub/dist/helpers"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; import { CaseTypes } from "../../../data/CaseTypes"; import { LogType } from "../../../data/LogType"; +import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; +import { canActOn, hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { resolveMember, resolveUser } from "../../../utils"; import { banLock } from "../../../utils/lockNameHelpers"; +import { waitForButtonConfirm } from "../../../utils/waitForInteraction"; +import { banUserId } from "../functions/banUserId"; +import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; +import { isBanned } from "../functions/isBanned"; +import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs"; +import { modActionsCmd } from "../types"; const opts = { mod: ct.member({ option: true }), @@ -49,7 +51,7 @@ export const BanCmd = modActionsCmd({ } const time = args["time"] ? args["time"] : null; - const reason = formatReasonWithAttachments(args.reason, msg.attachments); + const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]); const memberToBan = await resolveMember(pluginData.client, pluginData.guild, user.id); // The moderator who did the action is the message author or, if used, the specified -mod let mod = msg.member; @@ -76,11 +78,12 @@ export const BanCmd = modActionsCmd({ } // Ask the mod if we should update the existing ban - const alreadyBannedMsg = await msg.channel.createMessage("User is already banned, update ban?"); - const reply = await waitForReaction(pluginData.client, alreadyBannedMsg, ["✅", "❌"], msg.author.id); - - alreadyBannedMsg.delete().catch(noop); - if (!reply || reply.name === "❌") { + const reply = await waitForButtonConfirm( + msg.channel, + { content: "Failed to message the user. Log the warning anyway?" }, + { confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id }, + ); + if (!reply) { sendErrorMessage(pluginData, msg.channel, "User already banned, update cancelled by moderator"); lock.unlock(); return; @@ -107,8 +110,8 @@ export const BanCmd = modActionsCmd({ }); const logtype = time ? LogType.MEMBER_TIMED_BAN : LogType.MEMBER_BAN; pluginData.state.serverLogs.log(logtype, { - mod: stripObjectToScalars(mod.user), - user: stripObjectToScalars(user), + mod: userToConfigAccessibleUser(mod.user), + user: userToConfigAccessibleUser(user), caseNumber: createdCase.case_number, reason, banTime: time ? humanizeDuration(time) : null, @@ -124,11 +127,12 @@ export const BanCmd = modActionsCmd({ } } else { // Ask the mod if we should upgrade to a forceban as the user is not on the server - const notOnServerMsg = await msg.channel.createMessage("User not found on the server, forceban instead?"); - const reply = await waitForReaction(pluginData.client, notOnServerMsg, ["✅", "❌"], msg.author.id); - - notOnServerMsg.delete().catch(noop); - if (!reply || reply.name === "❌") { + const reply = await waitForButtonConfirm( + msg.channel, + { content: "User not on server, forceban instead?" }, + { confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id }, + ); + if (!reply) { sendErrorMessage(pluginData, msg.channel, "User not on server, ban cancelled by moderator"); lock.unlock(); return; @@ -192,7 +196,7 @@ export const BanCmd = modActionsCmd({ // Confirm the action to the moderator let response = ""; if (!forceban) { - response = `Banned **${user.username}#${user.discriminator}** ${forTime}(Case #${banResult.case.case_number})`; + response = `Banned **${user.tag}** ${forTime}(Case #${banResult.case.case_number})`; if (banResult.notifyResult.text) response += ` (${banResult.notifyResult.text})`; } else { response = `Member forcebanned ${forTime}(Case #${banResult.case.case_number})`; diff --git a/backend/src/plugins/ModActions/commands/CaseCmd.ts b/backend/src/plugins/ModActions/commands/CaseCmd.ts index ff5932c5..d3d8ce95 100644 --- a/backend/src/plugins/ModActions/commands/CaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/CaseCmd.ts @@ -1,7 +1,7 @@ -import { modActionsCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; +import { sendErrorMessage } from "../../../pluginUtils"; +import { modActionsCmd } from "../types"; export const CaseCmd = modActionsCmd({ trigger: "case", @@ -24,6 +24,6 @@ export const CaseCmd = modActionsCmd({ const casesPlugin = pluginData.getPlugin(CasesPlugin); const embed = await casesPlugin.getCaseEmbed(theCase.id, msg.author.id); - msg.channel.createMessage(embed); + msg.channel.send(embed); }, }); diff --git a/backend/src/plugins/ModActions/commands/CasesModCmd.ts b/backend/src/plugins/ModActions/commands/CasesModCmd.ts index f9f4667d..45acbb73 100644 --- a/backend/src/plugins/ModActions/commands/CasesModCmd.ts +++ b/backend/src/plugins/ModActions/commands/CasesModCmd.ts @@ -1,14 +1,13 @@ -import { modActionsCmd } from "../types"; +import { MessageEmbedOptions, User } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage } from "../../../pluginUtils"; -import { trimLines, createChunkedMessage, emptyEmbedValue, sorter, resolveUser } from "../../../utils"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { emptyEmbedValue, resolveUser, trimLines } from "../../../utils"; import { asyncMap } from "../../../utils/async"; -import { EmbedOptions, User } from "eris"; -import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields"; -import { getDefaultPrefix } from "knub/dist/commands/commandUtils"; -import { getGuildPrefix } from "../../../utils/getGuildPrefix"; import { createPaginatedMessage } from "../../../utils/createPaginatedMessage"; +import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields"; +import { getGuildPrefix } from "../../../utils/getGuildPrefix"; +import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { modActionsCmd } from "../types"; const opts = { mod: ct.userId({ option: true }), @@ -30,7 +29,7 @@ export const CasesModCmd = modActionsCmd({ async run({ pluginData, message: msg, args }) { const modId = args.mod || msg.author.id; const mod = await resolveUser(pluginData.client, modId); - const modName = mod instanceof User ? `${mod.username}#${mod.discriminator}` : modId; + const modName = mod instanceof User ? mod.tag : modId; const casesPlugin = pluginData.getPlugin(CasesPlugin); const totalCases = await casesPlugin.getTotalCasesByMod(modId); @@ -55,10 +54,10 @@ export const CasesModCmd = modActionsCmd({ const lastCaseNum = page * casesPerPage; const title = `Most recent cases ${firstCaseNum}-${lastCaseNum} of ${totalCases} by ${modName}`; - const embed: EmbedOptions = { + const embed: MessageEmbedOptions = { author: { name: title, - icon_url: mod instanceof User ? mod.avatarURL || mod.defaultAvatarURL : undefined, + iconURL: mod instanceof User ? mod.displayAvatarURL() : undefined, }, fields: [ ...getChunkedEmbedFields(emptyEmbedValue, lines.join("\n")), @@ -72,7 +71,7 @@ export const CasesModCmd = modActionsCmd({ ], }; - return { embed }; + return { embeds: [embed] }; }, { limitToUserId: msg.author.id, diff --git a/backend/src/plugins/ModActions/commands/CasesUserCmd.ts b/backend/src/plugins/ModActions/commands/CasesUserCmd.ts index b3da7899..39d4050b 100644 --- a/backend/src/plugins/ModActions/commands/CasesUserCmd.ts +++ b/backend/src/plugins/ModActions/commands/CasesUserCmd.ts @@ -1,32 +1,24 @@ -import { modActionsCmd } from "../types"; +import { MessageEmbedOptions, User } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; -import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; -import { - UnknownUser, - multiSorter, - trimLines, - createChunkedMessage, - resolveUser, - emptyEmbedValue, - chunkArray, -} from "../../../utils"; -import { getGuildPrefix } from "../../../utils/getGuildPrefix"; -import { EmbedOptions, User } from "eris"; -import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields"; -import { asyncMap } from "../../../utils/async"; import { CaseTypes } from "../../../data/CaseTypes"; +import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; +import { sendErrorMessage } from "../../../pluginUtils"; +import { chunkArray, emptyEmbedValue, resolveUser, trimLines, UnknownUser } from "../../../utils"; +import { asyncMap } from "../../../utils/async"; +import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields"; +import { getGuildPrefix } from "../../../utils/getGuildPrefix"; +import { modActionsCmd } from "../types"; const opts = { expand: ct.bool({ option: true, isSwitch: true, shortcut: "e" }), hidden: ct.bool({ option: true, isSwitch: true, shortcut: "h" }), - reverseFilters: ct.switchOption({ shortcut: "r" }), - notes: ct.switchOption({ shortcut: "n" }), - warns: ct.switchOption({ shortcut: "w" }), - mutes: ct.switchOption({ shortcut: "m" }), - unmutes: ct.switchOption({ shortcut: "um" }), - bans: ct.switchOption({ shortcut: "b" }), - unbans: ct.switchOption({ shortcut: "ub" }), + reverseFilters: ct.switchOption({ def: false, shortcut: "r" }), + notes: ct.switchOption({ def: false, shortcut: "n" }), + warns: ct.switchOption({ def: false, shortcut: "w" }), + mutes: ct.switchOption({ def: false, shortcut: "m" }), + unmutes: ct.switchOption({ def: false, shortcut: "um" }), + bans: ct.switchOption({ def: false, shortcut: "b" }), + unbans: ct.switchOption({ def: false, shortcut: "ub" }), }; export const CasesUserCmd = modActionsCmd({ @@ -69,19 +61,16 @@ export const CasesUserCmd = modActionsCmd({ const normalCases = cases.filter(c => !c.is_hidden); const hiddenCases = cases.filter(c => c.is_hidden); - const userName = - user instanceof UnknownUser && cases.length - ? cases[cases.length - 1].user_name - : `${user.username}#${user.discriminator}`; + const userName = user instanceof UnknownUser && cases.length ? cases[cases.length - 1].user_name : user.tag; if (cases.length === 0) { - msg.channel.createMessage(`No cases found for **${userName}**`); + msg.channel.send(`No cases found for **${userName}**`); } else { const casesToDisplay = args.hidden ? cases : normalCases; if (args.expand) { if (casesToDisplay.length > 8) { - msg.channel.createMessage("Too many cases for expanded view. Please use compact view instead."); + msg.channel.send("Too many cases for expanded view. Please use compact view instead."); return; } @@ -89,7 +78,7 @@ export const CasesUserCmd = modActionsCmd({ const casesPlugin = pluginData.getPlugin(CasesPlugin); for (const theCase of casesToDisplay) { const embed = await casesPlugin.getCaseEmbed(theCase.id); - msg.channel.createMessage(embed); + msg.channel.send(embed); } } else { // Compact view (= regular message with a preview of each case) @@ -121,13 +110,13 @@ export const CasesUserCmd = modActionsCmd({ const chunkStart = i * linesPerChunk + 1; const chunkEnd = Math.min((i + 1) * linesPerChunk, lines.length); - const embed: EmbedOptions = { + const embed: MessageEmbedOptions = { author: { name: lineChunks.length === 1 ? `Cases for ${userName} (${lines.length} total)` : `Cases ${chunkStart}–${chunkEnd} of ${lines.length} for ${userName}`, - icon_url: user instanceof User ? user.avatarURL || user.defaultAvatarURL : undefined, + icon_url: user instanceof User ? user.displayAvatarURL() : undefined, }, fields: [ ...getChunkedEmbedFields(emptyEmbedValue, linesInChunk.join("\n")), @@ -135,7 +124,7 @@ export const CasesUserCmd = modActionsCmd({ ], }; - msg.channel.createMessage({ embed }); + msg.channel.send({ embeds: [embed] }); } } } diff --git a/backend/src/plugins/ModActions/commands/DeleteCaseCmd.ts b/backend/src/plugins/ModActions/commands/DeleteCaseCmd.ts index 519f3a28..925d97c6 100644 --- a/backend/src/plugins/ModActions/commands/DeleteCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/DeleteCaseCmd.ts @@ -1,15 +1,15 @@ -import { modActionsCmd } from "../types"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { TextChannel } from "discord.js"; import { helpers } from "knub"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; -import { TextChannel } from "eris"; -import { SECONDS, stripObjectToScalars, trimLines } from "../../../utils"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { LogType } from "../../../data/LogType"; -import moment from "moment-timezone"; -import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { memberToConfigAccessibleMember } from "../../../utils/configAccessibleObjects"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; import { Case } from "../../../data/entities/Case"; +import { LogType } from "../../../data/LogType"; +import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { SECONDS, stripObjectToScalars, trimLines } from "../../../utils"; +import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { modActionsCmd } from "../types"; export const DeleteCaseCmd = modActionsCmd({ trigger: ["delete_case", "deletecase"], @@ -22,7 +22,7 @@ export const DeleteCaseCmd = modActionsCmd({ signature: { caseNumber: ct.number({ rest: true }), - force: ct.switchOption({ shortcut: "f" }), + force: ct.switchOption({ def: false, shortcut: "f" }), }, async run({ pluginData, message, args }) { @@ -49,9 +49,9 @@ export const DeleteCaseCmd = modActionsCmd({ if (!args.force) { const cases = pluginData.getPlugin(CasesPlugin); const embedContent = await cases.getCaseEmbed(theCase); - message.channel.createMessage({ + message.channel.send({ + ...embedContent, content: "Delete the following case? Answer 'Yes' to continue, 'No' to cancel.", - embed: embedContent.embed, }); const reply = await helpers.waitForReply( @@ -62,13 +62,13 @@ export const DeleteCaseCmd = modActionsCmd({ ); const normalizedReply = (reply?.content || "").toLowerCase().trim(); if (normalizedReply !== "yes" && normalizedReply !== "y") { - message.channel.createMessage("Cancelled. Case was not deleted."); + message.channel.send("Cancelled. Case was not deleted."); cancelled++; continue; } } - const deletedByName = `${message.author.username}#${message.author.discriminator}`; + const deletedByName = message.author.tag; const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin); const deletedAt = timeAndDate.inGuildTz().format(timeAndDate.getDateFormat("pretty_datetime")); @@ -82,7 +82,7 @@ export const DeleteCaseCmd = modActionsCmd({ const logs = pluginData.getPlugin(LogsPlugin); logs.log(LogType.CASE_DELETE, { - mod: stripObjectToScalars(message.member, ["user", "roles"]), + mod: memberToConfigAccessibleMember(message.member), case: stripObjectToScalars(theCase), }); } diff --git a/backend/src/plugins/ModActions/commands/ForcebanCmd.ts b/backend/src/plugins/ModActions/commands/ForcebanCmd.ts index cdbbbc97..8a1df43e 100644 --- a/backend/src/plugins/ModActions/commands/ForcebanCmd.ts +++ b/backend/src/plugins/ModActions/commands/ForcebanCmd.ts @@ -1,15 +1,15 @@ -import { modActionsCmd, IgnoredEventType } from "../types"; +import { Snowflake } from "discord.js"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canActOn, sendErrorMessage, hasPermission, sendSuccessMessage } from "../../../pluginUtils"; -import { resolveUser, resolveMember, stripObjectToScalars } from "../../../utils"; -import { isBanned } from "../functions/isBanned"; -import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { banUserId } from "../functions/banUserId"; -import { ignoreEvent } from "../functions/ignoreEvent"; -import { LogType } from "../../../data/LogType"; import { CaseTypes } from "../../../data/CaseTypes"; +import { LogType } from "../../../data/LogType"; import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; +import { canActOn, hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { resolveMember, resolveUser } from "../../../utils"; +import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; +import { ignoreEvent } from "../functions/ignoreEvent"; +import { isBanned } from "../functions/isBanned"; +import { IgnoredEventType, modActionsCmd } from "../types"; const opts = { mod: ct.member({ option: true }), @@ -61,14 +61,17 @@ export const ForcebanCmd = modActionsCmd({ mod = args.mod; } - const reason = formatReasonWithAttachments(args.reason, msg.attachments); + const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]); ignoreEvent(pluginData, IgnoredEventType.Ban, user.id); pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_BAN, user.id); try { // FIXME: Use banUserId()? - await pluginData.guild.banMember(user.id, 1, reason != null ? encodeURIComponent(reason) : undefined); + await pluginData.guild.bans.create(user.id as Snowflake, { + days: 1, + reason: reason ?? undefined, + }); } catch { sendErrorMessage(pluginData, msg.channel, "Failed to forceban member"); return; @@ -89,7 +92,7 @@ export const ForcebanCmd = modActionsCmd({ // Log the action pluginData.state.serverLogs.log(LogType.MEMBER_FORCEBAN, { - mod: stripObjectToScalars(mod.user), + mod: userToConfigAccessibleUser(mod.user), userId: user.id, caseNumber: createdCase.case_number, reason, diff --git a/backend/src/plugins/ModActions/commands/ForcemuteCmd.ts b/backend/src/plugins/ModActions/commands/ForcemuteCmd.ts index af680374..18fe1228 100644 --- a/backend/src/plugins/ModActions/commands/ForcemuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/ForcemuteCmd.ts @@ -1,8 +1,8 @@ -import { modActionsCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { canActOn, sendErrorMessage } from "../../../pluginUtils"; import { resolveMember, resolveUser } from "../../../utils"; import { actualMuteUserCmd } from "../functions/actualMuteUserCmd"; +import { modActionsCmd } from "../types"; const opts = { mod: ct.member({ option: true }), diff --git a/backend/src/plugins/ModActions/commands/ForceunmuteCmd.ts b/backend/src/plugins/ModActions/commands/ForceunmuteCmd.ts index e86a6408..8ce0ce14 100644 --- a/backend/src/plugins/ModActions/commands/ForceunmuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/ForceunmuteCmd.ts @@ -1,8 +1,8 @@ -import { modActionsCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { canActOn, sendErrorMessage } from "../../../pluginUtils"; -import { resolveUser, resolveMember } from "../../../utils"; +import { resolveMember, resolveUser } from "../../../utils"; import { actualUnmuteCmd } from "../functions/actualUnmuteUserCmd"; +import { modActionsCmd } from "../types"; const opts = { mod: ct.member({ option: true }), diff --git a/backend/src/plugins/ModActions/commands/HideCaseCmd.ts b/backend/src/plugins/ModActions/commands/HideCaseCmd.ts index 0e12521f..38337d85 100644 --- a/backend/src/plugins/ModActions/commands/HideCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/HideCaseCmd.ts @@ -1,6 +1,6 @@ -import { modActionsCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { modActionsCmd } from "../types"; export const HideCaseCmd = modActionsCmd({ trigger: ["hide", "hidecase", "hide_case"], diff --git a/backend/src/plugins/ModActions/commands/KickCmd.ts b/backend/src/plugins/ModActions/commands/KickCmd.ts index d93e0369..080294d0 100644 --- a/backend/src/plugins/ModActions/commands/KickCmd.ts +++ b/backend/src/plugins/ModActions/commands/KickCmd.ts @@ -1,11 +1,6 @@ -import { modActionsCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canActOn, sendErrorMessage } from "../../../pluginUtils"; -import { resolveUser, resolveMember } from "../../../utils"; -import { MutesPlugin } from "../../../plugins/Mutes/MutesPlugin"; -import { actualUnmuteCmd } from "../functions/actualUnmuteUserCmd"; -import { isBanned } from "../functions/isBanned"; import { actualKickMemberCmd } from "../functions/actualKickMemberCmd"; +import { modActionsCmd } from "../types"; const opts = { mod: ct.member({ option: true }), diff --git a/backend/src/plugins/ModActions/commands/MassBanCmd.ts b/backend/src/plugins/ModActions/commands/MassBanCmd.ts index 93fa7f9c..e4e4fd44 100644 --- a/backend/src/plugins/ModActions/commands/MassBanCmd.ts +++ b/backend/src/plugins/ModActions/commands/MassBanCmd.ts @@ -1,20 +1,17 @@ -import { modActionsCmd, IgnoredEventType } from "../types"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canActOn, sendErrorMessage, hasPermission, sendSuccessMessage } from "../../../pluginUtils"; -import { resolveUser, resolveMember, stripObjectToScalars, noop, MINUTES } from "../../../utils"; -import { isBanned } from "../functions/isBanned"; -import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { banUserId } from "../functions/banUserId"; -import { CaseTypes } from "../../../data/CaseTypes"; -import { TextChannel } from "eris"; +import { Snowflake, TextChannel } from "discord.js"; import { waitForReply } from "knub/dist/helpers"; -import { ignoreEvent } from "../functions/ignoreEvent"; -import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; -import { LogType } from "../../../data/LogType"; import { performance } from "perf_hooks"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { CaseTypes } from "../../../data/CaseTypes"; +import { LogType } from "../../../data/LogType"; import { humanizeDurationShort } from "../../../humanizeDurationShort"; -import { load } from "js-yaml"; +import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; +import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { MINUTES, noop } from "../../../utils"; +import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; +import { ignoreEvent } from "../functions/ignoreEvent"; +import { IgnoredEventType, modActionsCmd } from "../types"; export const MassbanCmd = modActionsCmd({ trigger: "massban", @@ -35,18 +32,18 @@ export const MassbanCmd = modActionsCmd({ } // Ask for ban reason (cleaner this way instead of trying to cram it into the args) - msg.channel.createMessage("Ban reason? `cancel` to cancel"); + msg.channel.send("Ban reason? `cancel` to cancel"); const banReasonReply = await waitForReply(pluginData.client, msg.channel as TextChannel, msg.author.id); if (!banReasonReply || !banReasonReply.content || banReasonReply.content.toLowerCase().trim() === "cancel") { sendErrorMessage(pluginData, msg.channel, "Cancelled"); return; } - const banReason = formatReasonWithAttachments(banReasonReply.content, msg.attachments); + const banReason = formatReasonWithAttachments(banReasonReply.content, [...msg.attachments.values()]); // Verify we can act on each of the users specified for (const userId of args.userIds) { - const member = pluginData.guild.members.get(userId); // TODO: Get members on demand? + const member = pluginData.guild.members.cache.get(userId as Snowflake); // TODO: Get members on demand? if (member && !canActOn(pluginData, msg.member, member)) { sendErrorMessage(pluginData, msg.channel, "Cannot massban one or more users: insufficient permissions"); return; @@ -60,7 +57,7 @@ export const MassbanCmd = modActionsCmd({ pluginData.state.massbanQueue.length === 0 ? "Banning..." : `Massban queued. Waiting for previous massban to finish (max wait ${maxWaitTimeFormatted}).`; - const loadingMsg = await msg.channel.createMessage(initialLoadingText); + const loadingMsg = await msg.channel.send(initialLoadingText); const waitTimeStart = performance.now(); const waitingInterval = setInterval(() => { @@ -84,6 +81,7 @@ export const MassbanCmd = modActionsCmd({ const startTime = performance.now(); const failedBans: string[] = []; const casesPlugin = pluginData.getPlugin(CasesPlugin); + const deleteDays = (await pluginData.config.getForMessage(msg)).ban_delete_message_days; for (const [i, userId] of args.userIds.entries()) { if (pluginData.state.unloaded) { break; @@ -95,7 +93,10 @@ export const MassbanCmd = modActionsCmd({ ignoreEvent(pluginData, IgnoredEventType.Ban, userId, 120 * 1000); pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_BAN, userId, 120 * 1000); - await pluginData.guild.banMember(userId, 1, banReason != null ? encodeURIComponent(banReason) : undefined); + await pluginData.guild.bans.create(userId as Snowflake, { + days: deleteDays, + reason: banReason ?? undefined, + }); await casesPlugin.createCase({ userId, @@ -129,7 +130,7 @@ export const MassbanCmd = modActionsCmd({ } else { // Some or all bans were successful. Create a log entry for the mass ban and notify the user. pluginData.state.serverLogs.log(LogType.MASSBAN, { - mod: stripObjectToScalars(msg.author), + mod: userToConfigAccessibleUser(msg.author), count: successfulBanCount, reason: banReason, }); diff --git a/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts b/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts index e11289c8..ea5e5770 100644 --- a/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts +++ b/backend/src/plugins/ModActions/commands/MassUnbanCmd.ts @@ -1,15 +1,15 @@ -import { modActionsCmd, IgnoredEventType } from "../types"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { stripObjectToScalars } from "../../../utils"; -import { isBanned } from "../functions/isBanned"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { CaseTypes } from "../../../data/CaseTypes"; -import { TextChannel } from "eris"; +import { Snowflake, TextChannel } from "discord.js"; import { waitForReply } from "knub/dist/helpers"; -import { ignoreEvent } from "../functions/ignoreEvent"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { CaseTypes } from "../../../data/CaseTypes"; import { LogType } from "../../../data/LogType"; +import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; +import { ignoreEvent } from "../functions/ignoreEvent"; +import { isBanned } from "../functions/isBanned"; +import { IgnoredEventType, modActionsCmd } from "../types"; export const MassunbanCmd = modActionsCmd({ trigger: "massunban", @@ -30,14 +30,14 @@ export const MassunbanCmd = modActionsCmd({ } // Ask for unban reason (cleaner this way instead of trying to cram it into the args) - msg.channel.createMessage("Unban reason? `cancel` to cancel"); + msg.channel.send("Unban reason? `cancel` to cancel"); const unbanReasonReply = await waitForReply(pluginData.client, msg.channel as TextChannel, msg.author.id); if (!unbanReasonReply || !unbanReasonReply.content || unbanReasonReply.content.toLowerCase().trim() === "cancel") { sendErrorMessage(pluginData, msg.channel, "Cancelled"); return; } - const unbanReason = formatReasonWithAttachments(unbanReasonReply.content, msg.attachments); + const unbanReason = formatReasonWithAttachments(unbanReasonReply.content, [...msg.attachments.values()]); // Ignore automatic unban cases and logs for these users // We'll create our own cases below and post a single "mass unbanned" log instead @@ -48,7 +48,7 @@ export const MassunbanCmd = modActionsCmd({ }); // Show a loading indicator since this can take a while - const loadingMsg = await msg.channel.createMessage("Unbanning..."); + const loadingMsg = await msg.channel.send("Unbanning..."); // Unban each user and count failed unbans (if any) const failedUnbans: Array<{ userId: string; reason: UnbanFailReasons }> = []; @@ -60,7 +60,7 @@ export const MassunbanCmd = modActionsCmd({ } try { - await pluginData.guild.unbanMember(userId, unbanReason != null ? encodeURIComponent(unbanReason) : undefined); + await pluginData.guild.bans.remove(userId as Snowflake, unbanReason ?? undefined); await casesPlugin.createCase({ userId, @@ -84,7 +84,7 @@ export const MassunbanCmd = modActionsCmd({ } else { // Some or all unbans were successful. Create a log entry for the mass unban and notify the user. pluginData.state.serverLogs.log(LogType.MASSUNBAN, { - mod: stripObjectToScalars(msg.author), + mod: userToConfigAccessibleUser(msg.author), count: successfulUnbanCount, reason: unbanReason, }); diff --git a/backend/src/plugins/ModActions/commands/MassmuteCmd.ts b/backend/src/plugins/ModActions/commands/MassmuteCmd.ts index 73e329d0..5c82dd5b 100644 --- a/backend/src/plugins/ModActions/commands/MassmuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/MassmuteCmd.ts @@ -1,13 +1,13 @@ -import { modActionsCmd } from "../types"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { stripObjectToScalars } from "../../../utils"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { TextChannel } from "eris"; +import { Snowflake, TextChannel } from "discord.js"; import { waitForReply } from "knub/dist/helpers"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; import { LogType } from "../../../data/LogType"; import { logger } from "../../../logger"; import { MutesPlugin } from "../../../plugins/Mutes/MutesPlugin"; +import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; +import { modActionsCmd } from "../types"; export const MassmuteCmd = modActionsCmd({ trigger: "massmute", @@ -28,7 +28,7 @@ export const MassmuteCmd = modActionsCmd({ } // Ask for mute reason - msg.channel.createMessage("Mute reason? `cancel` to cancel"); + msg.channel.send("Mute reason? `cancel` to cancel"); const muteReasonReceived = await waitForReply(pluginData.client, msg.channel as TextChannel, msg.author.id); if ( !muteReasonReceived || @@ -39,11 +39,11 @@ export const MassmuteCmd = modActionsCmd({ return; } - const muteReason = formatReasonWithAttachments(muteReasonReceived.content, msg.attachments); + const muteReason = formatReasonWithAttachments(muteReasonReceived.content, [...msg.attachments.values()]); // Verify we can act upon all users for (const userId of args.userIds) { - const member = pluginData.guild.members.get(userId); + const member = pluginData.guild.members.cache.get(userId as Snowflake); if (member && !canActOn(pluginData, msg.member, member)) { sendErrorMessage(pluginData, msg.channel, "Cannot massmute one or more users: insufficient permissions"); return; @@ -58,7 +58,7 @@ export const MassmuteCmd = modActionsCmd({ }); // Show loading indicator - const loadingMsg = await msg.channel.createMessage("Muting..."); + const loadingMsg = await msg.channel.send("Muting..."); // Mute everyone and count fails const modId = msg.author.id; @@ -87,7 +87,7 @@ export const MassmuteCmd = modActionsCmd({ } else { // Success on all or some mutes pluginData.state.serverLogs.log(LogType.MASSMUTE, { - mod: stripObjectToScalars(msg.author), + mod: userToConfigAccessibleUser(msg.author), count: successfulMuteCount, }); diff --git a/backend/src/plugins/ModActions/commands/MuteCmd.ts b/backend/src/plugins/ModActions/commands/MuteCmd.ts index 3aa96c38..f824fca3 100644 --- a/backend/src/plugins/ModActions/commands/MuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/MuteCmd.ts @@ -1,18 +1,10 @@ -import { modActionsCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { Case } from "../../../data/entities/Case"; -import { canActOn, hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; -import { LogType } from "../../../data/LogType"; -import { CaseTypes } from "../../../data/CaseTypes"; -import { errorMessage, noop, resolveMember, resolveUser, stripObjectToScalars } from "../../../utils"; -import { isBanned } from "../functions/isBanned"; -import { waitForReaction } from "knub/dist/helpers"; -import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs"; -import { warnMember } from "../functions/warnMember"; -import { TextChannel } from "eris"; +import { canActOn, sendErrorMessage } from "../../../pluginUtils"; +import { resolveMember, resolveUser } from "../../../utils"; +import { waitForButtonConfirm } from "../../../utils/waitForInteraction"; import { actualMuteUserCmd } from "../functions/actualMuteUserCmd"; +import { isBanned } from "../functions/isBanned"; +import { modActionsCmd } from "../types"; const opts = { mod: ct.member({ option: true }), @@ -62,11 +54,13 @@ export const MuteCmd = modActionsCmd({ return; } else { // Ask the mod if we should upgrade to a forcemute as the user is not on the server - const notOnServerMsg = await msg.channel.createMessage("User not found on the server, forcemute instead?"); - const reply = await waitForReaction(pluginData.client, notOnServerMsg, ["✅", "❌"], msg.author.id); + const reply = await waitForButtonConfirm( + msg.channel, + { content: "User not found on the server, forcemute instead?" }, + { confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id }, + ); - notOnServerMsg.delete().catch(noop); - if (!reply || reply.name === "❌") { + if (!reply) { sendErrorMessage(pluginData, msg.channel, "User not on server, mute cancelled by moderator"); return; } diff --git a/backend/src/plugins/ModActions/commands/NoteCmd.ts b/backend/src/plugins/ModActions/commands/NoteCmd.ts index af6a1298..92e1b0fa 100644 --- a/backend/src/plugins/ModActions/commands/NoteCmd.ts +++ b/backend/src/plugins/ModActions/commands/NoteCmd.ts @@ -1,12 +1,12 @@ -import { modActionsCmd } from "../types"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { Case } from "../../../data/entities/Case"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; -import { LogType } from "../../../data/LogType"; import { CaseTypes } from "../../../data/CaseTypes"; -import { resolveUser, stripObjectToScalars } from "../../../utils"; +import { LogType } from "../../../data/LogType"; +import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { resolveUser } from "../../../utils"; +import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; +import { modActionsCmd } from "../types"; export const NoteCmd = modActionsCmd({ trigger: "note", @@ -25,13 +25,13 @@ export const NoteCmd = modActionsCmd({ return; } - if (!args.note && msg.attachments.length === 0) { + if (!args.note && msg.attachments.size === 0) { sendErrorMessage(pluginData, msg.channel, "Text or attachment required"); return; } - const userName = `${user.username}#${user.discriminator}`; - const reason = formatReasonWithAttachments(args.note, msg.attachments); + const userName = user.tag; + const reason = formatReasonWithAttachments(args.note, [...msg.attachments.values()]); const casesPlugin = pluginData.getPlugin(CasesPlugin); const createdCase = await casesPlugin.createCase({ @@ -42,8 +42,8 @@ export const NoteCmd = modActionsCmd({ }); pluginData.state.serverLogs.log(LogType.MEMBER_NOTE, { - mod: stripObjectToScalars(msg.author), - user: stripObjectToScalars(user, ["user", "roles"]), + mod: userToConfigAccessibleUser(msg.author), + user: userToConfigAccessibleUser(user), caseNumber: createdCase.case_number, reason, }); diff --git a/backend/src/plugins/ModActions/commands/SoftbanCommand.ts b/backend/src/plugins/ModActions/commands/SoftbanCommand.ts index 8105aae3..9bbb6415 100644 --- a/backend/src/plugins/ModActions/commands/SoftbanCommand.ts +++ b/backend/src/plugins/ModActions/commands/SoftbanCommand.ts @@ -1,7 +1,7 @@ -import { modActionsCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { trimPluginDescription } from "../../../utils"; import { actualKickMemberCmd } from "../functions/actualKickMemberCmd"; +import { modActionsCmd } from "../types"; const opts = { mod: ct.member({ option: true }), @@ -28,7 +28,7 @@ export const SoftbanCmd = modActionsCmd({ async run({ pluginData, message: msg, args }) { await actualKickMemberCmd(pluginData, msg, { clean: true, ...args }); - await msg.channel.createMessage( + await msg.channel.send( "Softban will be removed in the future - please use the kick command with the `-clean` argument instead!", ); }, diff --git a/backend/src/plugins/ModActions/commands/UnbanCmd.ts b/backend/src/plugins/ModActions/commands/UnbanCmd.ts index c3016a9b..0e199b25 100644 --- a/backend/src/plugins/ModActions/commands/UnbanCmd.ts +++ b/backend/src/plugins/ModActions/commands/UnbanCmd.ts @@ -1,12 +1,14 @@ -import { modActionsCmd, IgnoredEventType } from "../types"; +import { Snowflake } from "discord.js"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, hasPermission, sendSuccessMessage } from "../../../pluginUtils"; -import { resolveUser, stripObjectToScalars } from "../../../utils"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { LogType } from "../../../data/LogType"; -import { ignoreEvent } from "../functions/ignoreEvent"; import { CaseTypes } from "../../../data/CaseTypes"; +import { LogType } from "../../../data/LogType"; import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; +import { hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { resolveUser } from "../../../utils"; +import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; +import { ignoreEvent } from "../functions/ignoreEvent"; +import { IgnoredEventType, modActionsCmd } from "../types"; const opts = { mod: ct.member({ option: true }), @@ -45,11 +47,11 @@ export const UnbanCmd = modActionsCmd({ } pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, user.id); - const reason = formatReasonWithAttachments(args.reason, msg.attachments); + const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]); try { ignoreEvent(pluginData, IgnoredEventType.Unban, user.id); - await pluginData.guild.unbanMember(user.id, reason != null ? encodeURIComponent(reason) : undefined); + await pluginData.guild.bans.remove(user.id as Snowflake, reason ?? undefined); } catch { sendErrorMessage(pluginData, msg.channel, "Failed to unban member; are you sure they're banned?"); return; @@ -72,7 +74,7 @@ export const UnbanCmd = modActionsCmd({ // Log the action pluginData.state.serverLogs.log(LogType.MEMBER_UNBAN, { - mod: stripObjectToScalars(mod.user), + mod: userToConfigAccessibleUser(mod.user), userId: user.id, caseNumber: createdCase.case_number, reason, diff --git a/backend/src/plugins/ModActions/commands/UnhideCaseCmd.ts b/backend/src/plugins/ModActions/commands/UnhideCaseCmd.ts index 5418b2a5..9fe9e208 100644 --- a/backend/src/plugins/ModActions/commands/UnhideCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/UnhideCaseCmd.ts @@ -1,6 +1,6 @@ -import { modActionsCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { modActionsCmd } from "../types"; export const UnhideCaseCmd = modActionsCmd({ trigger: ["unhide", "unhidecase", "unhide_case"], diff --git a/backend/src/plugins/ModActions/commands/UnmuteCmd.ts b/backend/src/plugins/ModActions/commands/UnmuteCmd.ts index c294517c..f09c2e83 100644 --- a/backend/src/plugins/ModActions/commands/UnmuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/UnmuteCmd.ts @@ -1,11 +1,11 @@ -import { modActionsCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { canActOn, sendErrorMessage } from "../../../pluginUtils"; -import { resolveUser, resolveMember, noop } from "../../../utils"; import { MutesPlugin } from "../../../plugins/Mutes/MutesPlugin"; +import { canActOn, sendErrorMessage } from "../../../pluginUtils"; +import { resolveMember, resolveUser } from "../../../utils"; +import { waitForButtonConfirm } from "../../../utils/waitForInteraction"; import { actualUnmuteCmd } from "../functions/actualUnmuteUserCmd"; import { isBanned } from "../functions/isBanned"; -import { waitForReaction } from "knub/dist/helpers"; +import { modActionsCmd } from "../types"; const opts = { mod: ct.member({ option: true }), @@ -61,11 +61,13 @@ export const UnmuteCmd = modActionsCmd({ return; } else { // Ask the mod if we should upgrade to a forceunmute as the user is not on the server - const notOnServerMsg = await msg.channel.createMessage("User not found on the server, forceunmute instead?"); - const reply = await waitForReaction(pluginData.client, notOnServerMsg, ["✅", "❌"], msg.author.id); + const reply = await waitForButtonConfirm( + msg.channel, + { content: "User not on server, forceunmute instead?" }, + { confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id }, + ); - notOnServerMsg.delete().catch(noop); - if (!reply || reply.name === "❌") { + if (!reply) { sendErrorMessage(pluginData, msg.channel, "User not on server, unmute cancelled by moderator"); return; } diff --git a/backend/src/plugins/ModActions/commands/UpdateCmd.ts b/backend/src/plugins/ModActions/commands/UpdateCmd.ts index 6c8d78ca..3310522e 100644 --- a/backend/src/plugins/ModActions/commands/UpdateCmd.ts +++ b/backend/src/plugins/ModActions/commands/UpdateCmd.ts @@ -1,6 +1,6 @@ -import { modActionsCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { updateCase } from "../functions/updateCase"; +import { modActionsCmd } from "../types"; export const UpdateCmd = modActionsCmd({ trigger: ["update", "reason"], diff --git a/backend/src/plugins/ModActions/commands/WarnCmd.ts b/backend/src/plugins/ModActions/commands/WarnCmd.ts index 7fc92b49..03d79b76 100644 --- a/backend/src/plugins/ModActions/commands/WarnCmd.ts +++ b/backend/src/plugins/ModActions/commands/WarnCmd.ts @@ -1,17 +1,15 @@ -import { modActionsCmd } from "../types"; +import { TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { Case } from "../../../data/entities/Case"; -import { canActOn, hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; -import { LogType } from "../../../data/LogType"; import { CaseTypes } from "../../../data/CaseTypes"; -import { errorMessage, resolveMember, resolveUser, stripObjectToScalars } from "../../../utils"; +import { canActOn, hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { errorMessage, resolveMember, resolveUser } from "../../../utils"; +import { waitForButtonConfirm } from "../../../utils/waitForInteraction"; +import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; import { isBanned } from "../functions/isBanned"; -import { waitForReaction } from "knub/dist/helpers"; import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs"; import { warnMember } from "../functions/warnMember"; -import { TextChannel } from "eris"; +import { modActionsCmd } from "../types"; export const WarnCmd = modActionsCmd({ trigger: "warn", @@ -57,7 +55,7 @@ export const WarnCmd = modActionsCmd({ let mod = msg.member; if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - msg.channel.createMessage(errorMessage("You don't have permission to use -mod")); + msg.channel.send(errorMessage("You don't have permission to use -mod")); return; } @@ -65,19 +63,18 @@ export const WarnCmd = modActionsCmd({ } const config = pluginData.config.get(); - const reason = formatReasonWithAttachments(args.reason, msg.attachments); + const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]); const casesPlugin = pluginData.getPlugin(CasesPlugin); const priorWarnAmount = await casesPlugin.getCaseTypeAmountForUserId(memberToWarn.id, CaseTypes.Warn); if (config.warn_notify_enabled && priorWarnAmount >= config.warn_notify_threshold) { - const tooManyWarningsMsg = await msg.channel.createMessage( - config.warn_notify_message.replace("{priorWarnings}", `${priorWarnAmount}`), + const reply = await waitForButtonConfirm( + msg.channel, + { content: config.warn_notify_message.replace("{priorWarnings}", `${priorWarnAmount}`) }, + { confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id }, ); - - const reply = await waitForReaction(pluginData.client, tooManyWarningsMsg, ["✅", "❌"], msg.author.id); - tooManyWarningsMsg.delete(); - if (!reply || reply.name === "❌") { - msg.channel.createMessage(errorMessage("Warn cancelled by moderator")); + if (!reply) { + msg.channel.send(errorMessage("Warn cancelled by moderator")); return; } } @@ -110,7 +107,7 @@ export const WarnCmd = modActionsCmd({ sendSuccessMessage( pluginData, msg.channel, - `Warned **${memberToWarn.user.username}#${memberToWarn.user.discriminator}** (Case #${warnResult.case.case_number})${messageResultText}`, + `Warned **${memberToWarn.user.tag}** (Case #${warnResult.case.case_number})${messageResultText}`, ); }, }); diff --git a/backend/src/plugins/ModActions/events/CreateBanCaseOnManualBanEvt.ts b/backend/src/plugins/ModActions/events/CreateBanCaseOnManualBanEvt.ts index c3b10cdc..875f8cd7 100644 --- a/backend/src/plugins/ModActions/events/CreateBanCaseOnManualBanEvt.ts +++ b/backend/src/plugins/ModActions/events/CreateBanCaseOnManualBanEvt.ts @@ -1,13 +1,14 @@ -import { IgnoredEventType, modActionsEvt } from "../types"; -import { isEventIgnored } from "../functions/isEventIgnored"; -import { clearIgnoredEvents } from "../functions/clearIgnoredEvents"; -import { Constants as ErisConstants, User } from "eris"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { GuildAuditLogs, User } from "discord.js"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { CaseTypes } from "../../../data/CaseTypes"; -import { safeFindRelevantAuditLogEntry } from "../../../utils/safeFindRelevantAuditLogEntry"; -import { LogType } from "../../../data/LogType"; -import { stripObjectToScalars, resolveUser, UnknownUser } from "../../../utils"; import { Case } from "../../../data/entities/Case"; +import { LogType } from "../../../data/LogType"; +import { resolveUser, UnknownUser } from "../../../utils"; +import { safeFindRelevantAuditLogEntry } from "../../../utils/safeFindRelevantAuditLogEntry"; +import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { clearIgnoredEvents } from "../functions/clearIgnoredEvents"; +import { isEventIgnored } from "../functions/isEventIgnored"; +import { IgnoredEventType, modActionsEvt } from "../types"; /** * Create a BAN case automatically when a user is banned manually. @@ -15,7 +16,8 @@ import { Case } from "../../../data/entities/Case"; */ export const CreateBanCaseOnManualBanEvt = modActionsEvt({ event: "guildBanAdd", - async listener({ pluginData, args: { guild, user } }) { + async listener({ pluginData, args: { ban } }) { + const user = ban.user; if (isEventIgnored(pluginData, IgnoredEventType.Ban, user.id)) { clearIgnoredEvents(pluginData, IgnoredEventType.Ban, user.id); return; @@ -23,7 +25,7 @@ export const CreateBanCaseOnManualBanEvt = modActionsEvt({ const relevantAuditLogEntry = await safeFindRelevantAuditLogEntry( pluginData, - ErisConstants.AuditLogActions.MEMBER_BAN_ADD, + GuildAuditLogs.Actions.MEMBER_BAN_ADD as number, user.id, ); @@ -34,7 +36,7 @@ export const CreateBanCaseOnManualBanEvt = modActionsEvt({ let reason = ""; if (relevantAuditLogEntry) { - const modId = relevantAuditLogEntry.user.id; + const modId = relevantAuditLogEntry.executor!.id; const auditLogId = relevantAuditLogEntry.id; mod = await resolveUser(pluginData.client, modId); @@ -42,7 +44,7 @@ export const CreateBanCaseOnManualBanEvt = modActionsEvt({ const config = mod instanceof UnknownUser ? pluginData.config.get() : await pluginData.config.getForUser(mod); if (config.create_cases_for_manual_actions) { - reason = relevantAuditLogEntry.reason || ""; + reason = relevantAuditLogEntry.reason ?? ""; createdCase = await casesPlugin.createCase({ userId: user.id, modId, @@ -64,8 +66,8 @@ export const CreateBanCaseOnManualBanEvt = modActionsEvt({ } pluginData.state.serverLogs.log(LogType.MEMBER_BAN, { - mod: mod ? stripObjectToScalars(mod, ["user"]) : null, - user: stripObjectToScalars(user, ["user"]), + mod: mod ? userToConfigAccessibleUser(mod) : null, + user: userToConfigAccessibleUser(user), caseNumber: createdCase?.case_number ?? 0, reason, }); diff --git a/backend/src/plugins/ModActions/events/CreateKickCaseOnManualKickEvt.ts b/backend/src/plugins/ModActions/events/CreateKickCaseOnManualKickEvt.ts index 65cb7636..d69ede2a 100644 --- a/backend/src/plugins/ModActions/events/CreateKickCaseOnManualKickEvt.ts +++ b/backend/src/plugins/ModActions/events/CreateKickCaseOnManualKickEvt.ts @@ -1,14 +1,15 @@ -import { IgnoredEventType, modActionsEvt } from "../types"; -import { isEventIgnored } from "../functions/isEventIgnored"; -import { clearIgnoredEvents } from "../functions/clearIgnoredEvents"; -import { Constants as ErisConstants, User } from "eris"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { GuildAuditLogs, User } from "discord.js"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { CaseTypes } from "../../../data/CaseTypes"; -import { logger } from "../../../logger"; -import { LogType } from "../../../data/LogType"; -import { resolveUser, stripObjectToScalars, UnknownUser } from "../../../utils"; -import { safeFindRelevantAuditLogEntry } from "../../../utils/safeFindRelevantAuditLogEntry"; import { Case } from "../../../data/entities/Case"; +import { LogType } from "../../../data/LogType"; +import { logger } from "../../../logger"; +import { resolveUser, UnknownUser } from "../../../utils"; +import { safeFindRelevantAuditLogEntry } from "../../../utils/safeFindRelevantAuditLogEntry"; +import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { clearIgnoredEvents } from "../functions/clearIgnoredEvents"; +import { isEventIgnored } from "../functions/isEventIgnored"; +import { IgnoredEventType, modActionsEvt } from "../types"; /** * Create a KICK case automatically when a user is kicked manually. @@ -24,7 +25,7 @@ export const CreateKickCaseOnManualKickEvt = modActionsEvt({ const kickAuditLogEntry = await safeFindRelevantAuditLogEntry( pluginData, - ErisConstants.AuditLogActions.MEMBER_KICK, + GuildAuditLogs.Actions.MEMBER_KICK as number, member.id, ); @@ -40,7 +41,7 @@ export const CreateKickCaseOnManualKickEvt = modActionsEvt({ `Tried to create duplicate case for audit log entry ${kickAuditLogEntry.id}, existing case id ${createdCase.id}`, ); } else { - mod = await resolveUser(pluginData.client, kickAuditLogEntry.user.id); + mod = await resolveUser(pluginData.client, kickAuditLogEntry.executor!.id); const config = mod instanceof UnknownUser ? pluginData.config.get() : await pluginData.config.getForUser(mod); @@ -48,7 +49,7 @@ export const CreateKickCaseOnManualKickEvt = modActionsEvt({ const casesPlugin = pluginData.getPlugin(CasesPlugin); createdCase = await casesPlugin.createCase({ userId: member.id, - modId: kickAuditLogEntry.user.id, + modId: mod.id, type: CaseTypes.Kick, auditLogId: kickAuditLogEntry.id, reason: kickAuditLogEntry.reason || undefined, @@ -58,8 +59,8 @@ export const CreateKickCaseOnManualKickEvt = modActionsEvt({ } pluginData.state.serverLogs.log(LogType.MEMBER_KICK, { - user: stripObjectToScalars(member.user), - mod: mod ? stripObjectToScalars(mod) : null, + user: userToConfigAccessibleUser(member.user!), + mod: mod ? userToConfigAccessibleUser(mod) : null, caseNumber: createdCase?.case_number ?? 0, reason: kickAuditLogEntry.reason || "", }); diff --git a/backend/src/plugins/ModActions/events/CreateUnbanCaseOnManualUnbanEvt.ts b/backend/src/plugins/ModActions/events/CreateUnbanCaseOnManualUnbanEvt.ts index 52d132f3..83143606 100644 --- a/backend/src/plugins/ModActions/events/CreateUnbanCaseOnManualUnbanEvt.ts +++ b/backend/src/plugins/ModActions/events/CreateUnbanCaseOnManualUnbanEvt.ts @@ -1,13 +1,14 @@ -import { IgnoredEventType, modActionsEvt } from "../types"; -import { isEventIgnored } from "../functions/isEventIgnored"; -import { clearIgnoredEvents } from "../functions/clearIgnoredEvents"; -import { Constants as ErisConstants, User } from "eris"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { GuildAuditLogs, User } from "discord.js"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { CaseTypes } from "../../../data/CaseTypes"; -import { safeFindRelevantAuditLogEntry } from "../../../utils/safeFindRelevantAuditLogEntry"; -import { stripObjectToScalars, resolveUser, UnknownUser } from "../../../utils"; -import { LogType } from "../../../data/LogType"; import { Case } from "../../../data/entities/Case"; +import { LogType } from "../../../data/LogType"; +import { resolveUser, UnknownUser } from "../../../utils"; +import { safeFindRelevantAuditLogEntry } from "../../../utils/safeFindRelevantAuditLogEntry"; +import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { clearIgnoredEvents } from "../functions/clearIgnoredEvents"; +import { isEventIgnored } from "../functions/isEventIgnored"; +import { IgnoredEventType, modActionsEvt } from "../types"; /** * Create an UNBAN case automatically when a user is unbanned manually. @@ -15,7 +16,8 @@ import { Case } from "../../../data/entities/Case"; */ export const CreateUnbanCaseOnManualUnbanEvt = modActionsEvt({ event: "guildBanRemove", - async listener({ pluginData, args: { guild, user } }) { + async listener({ pluginData, args: { ban } }) { + const user = ban.user; if (isEventIgnored(pluginData, IgnoredEventType.Unban, user.id)) { clearIgnoredEvents(pluginData, IgnoredEventType.Unban, user.id); return; @@ -23,7 +25,7 @@ export const CreateUnbanCaseOnManualUnbanEvt = modActionsEvt({ const relevantAuditLogEntry = await safeFindRelevantAuditLogEntry( pluginData, - ErisConstants.AuditLogActions.MEMBER_BAN_REMOVE, + GuildAuditLogs.Actions.MEMBER_BAN_REMOVE as number, user.id, ); @@ -33,7 +35,7 @@ export const CreateUnbanCaseOnManualUnbanEvt = modActionsEvt({ let mod: User | UnknownUser | null = null; if (relevantAuditLogEntry) { - const modId = relevantAuditLogEntry.user.id; + const modId = relevantAuditLogEntry.executor!.id; const auditLogId = relevantAuditLogEntry.id; mod = await resolveUser(pluginData.client, modId); @@ -62,7 +64,7 @@ export const CreateUnbanCaseOnManualUnbanEvt = modActionsEvt({ } pluginData.state.serverLogs.log(LogType.MEMBER_UNBAN, { - mod: mod ? stripObjectToScalars(mod, ["user"]) : null, + mod: mod ? userToConfigAccessibleUser(mod) : null, userId: user.id, caseNumber: createdCase?.case_number ?? 0, }); diff --git a/backend/src/plugins/ModActions/events/PostAlertOnMemberJoinEvt.ts b/backend/src/plugins/ModActions/events/PostAlertOnMemberJoinEvt.ts index 00b4110e..136e41cc 100644 --- a/backend/src/plugins/ModActions/events/PostAlertOnMemberJoinEvt.ts +++ b/backend/src/plugins/ModActions/events/PostAlertOnMemberJoinEvt.ts @@ -1,16 +1,16 @@ -import { modActionsEvt } from "../types"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { Permissions, Snowflake, TextChannel } from "discord.js"; import { LogType } from "../../../data/LogType"; -import { Constants, TextChannel } from "eris"; import { resolveMember } from "../../../utils"; import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { modActionsEvt } from "../types"; /** * Show an alert if a member with prior notes joins the server */ export const PostAlertOnMemberJoinEvt = modActionsEvt({ event: "guildMemberAdd", - async listener({ pluginData, args: { guild, member } }) { + async listener({ pluginData, args: { member } }) { const config = pluginData.config.get(); if (!config.alert_on_rejoin) return; @@ -22,7 +22,7 @@ export const PostAlertOnMemberJoinEvt = modActionsEvt({ const logs = pluginData.getPlugin(LogsPlugin); if (actions.length) { - const alertChannel = pluginData.guild.channels.get(alertChannelId); + const alertChannel = pluginData.guild.channels.cache.get(alertChannelId as Snowflake); if (!alertChannel) { logs.log(LogType.BOT_ALERT, { body: `Unknown \`alert_channel\` configured for \`mod_actions\`: \`${alertChannelId}\``, @@ -37,17 +37,17 @@ export const PostAlertOnMemberJoinEvt = modActionsEvt({ return; } - const botMember = await resolveMember(pluginData.client, pluginData.guild, pluginData.client.user.id); - const botPerms = alertChannel.permissionsOf(botMember ?? pluginData.client.user.id); - if (!hasDiscordPermissions(botPerms, Constants.Permissions.sendMessages)) { + const botMember = await resolveMember(pluginData.client, pluginData.guild, pluginData.client.user!.id); + const botPerms = alertChannel.permissionsFor(botMember ?? pluginData.client.user!.id); + if (!hasDiscordPermissions(botPerms, Permissions.FLAGS.SEND_MESSAGES)) { logs.log(LogType.BOT_ALERT, { body: `Missing "Send Messages" permissions for the \`alert_channel\` configured in \`mod_actions\`: \`${alertChannelId}\``, }); return; } - await alertChannel.createMessage( - `<@!${member.id}> (${member.user.username}#${member.user.discriminator} \`${member.id}\`) joined with ${actions.length} prior record(s)`, + await alertChannel.send( + `<@!${member.id}> (${member.user.tag} \`${member.id}\`) joined with ${actions.length} prior record(s)`, ); } }, diff --git a/backend/src/plugins/ModActions/functions/actualKickMemberCmd.ts b/backend/src/plugins/ModActions/functions/actualKickMemberCmd.ts index bac7fa0a..92178edc 100644 --- a/backend/src/plugins/ModActions/functions/actualKickMemberCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualKickMemberCmd.ts @@ -1,15 +1,15 @@ -import { Member, TextChannel } from "eris"; -import { LogType } from "../../../data/LogType"; -import { IgnoredEventType, ModActionsPluginType } from "../types"; -import { errorMessage, resolveUser, resolveMember } from "../../../utils"; +import { GuildMember, TextChannel, ThreadChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { sendErrorMessage, canActOn, sendSuccessMessage } from "../../../pluginUtils"; import { hasPermission } from "knub/dist/helpers"; -import { readContactMethodsFromArgs } from "./readContactMethodsFromArgs"; +import { LogType } from "../../../data/LogType"; +import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { errorMessage, resolveMember, resolveUser } from "../../../utils"; +import { IgnoredEventType, ModActionsPluginType } from "../types"; import { formatReasonWithAttachments } from "./formatReasonWithAttachments"; -import { kickMember } from "./kickMember"; import { ignoreEvent } from "./ignoreEvent"; import { isBanned } from "./isBanned"; +import { kickMember } from "./kickMember"; +import { readContactMethodsFromArgs } from "./readContactMethodsFromArgs"; export async function actualKickMemberCmd( pluginData: GuildPluginData, @@ -17,9 +17,9 @@ export async function actualKickMemberCmd( args: { user: string; reason: string; - mod: Member; + mod: GuildMember; notify?: string; - "notify-channel"?: TextChannel; + "notify-channel"?: TextChannel | ThreadChannel; clean?: boolean; }, ) { @@ -82,7 +82,7 @@ export async function actualKickMemberCmd( ignoreEvent(pluginData, IgnoredEventType.Ban, memberToKick.id); try { - await memberToKick.ban(1, encodeURIComponent("kick -clean")); + await memberToKick.ban({ days: 1, reason: "kick -clean" }); } catch { sendErrorMessage(pluginData, msg.channel, "Failed to ban the user to clean messages (-clean)"); } @@ -91,19 +91,19 @@ export async function actualKickMemberCmd( ignoreEvent(pluginData, IgnoredEventType.Unban, memberToKick.id); try { - await pluginData.guild.unbanMember(memberToKick.id, encodeURIComponent("kick -clean")); + await pluginData.guild.bans.remove(memberToKick.id, "kick -clean"); } catch { sendErrorMessage(pluginData, msg.channel, "Failed to unban the user after banning them (-clean)"); } } if (kickResult.status === "failed") { - msg.channel.createMessage(errorMessage(`Failed to kick user`)); + msg.channel.send(errorMessage(`Failed to kick user`)); return; } // Confirm the action to the moderator - let response = `Kicked **${memberToKick.user.username}#${memberToKick.user.discriminator}** (Case #${kickResult.case.case_number})`; + let response = `Kicked **${memberToKick.user.tag}** (Case #${kickResult.case.case_number})`; if (kickResult.notifyResult.text) response += ` (${kickResult.notifyResult.text})`; sendSuccessMessage(pluginData, msg.channel, response); diff --git a/backend/src/plugins/ModActions/functions/actualMuteUserCmd.ts b/backend/src/plugins/ModActions/functions/actualMuteUserCmd.ts index aa5310b7..f790604a 100644 --- a/backend/src/plugins/ModActions/functions/actualMuteUserCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualMuteUserCmd.ts @@ -1,15 +1,15 @@ -import { GuildTextableChannel, Member, Message, TextChannel, User } from "eris"; -import { asSingleLine, isDiscordRESTError, UnknownUser } from "../../../utils"; -import { hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { GuildPluginData } from "knub"; -import { ModActionsPluginType } from "../types"; +import { GuildMember, Message, TextChannel, ThreadChannel, User } from "discord.js"; import humanizeDuration from "humanize-duration"; -import { formatReasonWithAttachments } from "./formatReasonWithAttachments"; -import { MuteResult } from "../../Mutes/types"; -import { MutesPlugin } from "../../Mutes/MutesPlugin"; -import { readContactMethodsFromArgs } from "./readContactMethodsFromArgs"; -import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; +import { GuildPluginData } from "knub"; import { logger } from "../../../logger"; +import { hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; +import { asSingleLine, isDiscordAPIError, UnknownUser } from "../../../utils"; +import { MutesPlugin } from "../../Mutes/MutesPlugin"; +import { MuteResult } from "../../Mutes/types"; +import { ModActionsPluginType } from "../types"; +import { formatReasonWithAttachments } from "./formatReasonWithAttachments"; +import { readContactMethodsFromArgs } from "./readContactMethodsFromArgs"; /** * The actual function run by both !mute and !forcemute. @@ -18,16 +18,22 @@ import { logger } from "../../../logger"; export async function actualMuteUserCmd( pluginData: GuildPluginData, user: User | UnknownUser, - msg: Message, - args: { time?: number; reason?: string; mod: Member; notify?: string; "notify-channel"?: TextChannel }, + msg: Message, + args: { + time?: number; + reason?: string; + mod: GuildMember; + notify?: string; + "notify-channel"?: TextChannel | ThreadChannel; + }, ) { // The moderator who did the action is the message author or, if used, the specified -mod - let mod: Member = msg.member; + let mod: GuildMember = msg.member!; let pp: User | null = null; if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "You don't have permission to use -mod"); return; } @@ -36,7 +42,7 @@ export async function actualMuteUserCmd( } const timeUntilUnmute = args.time && humanizeDuration(args.time); - const reason = args.reason ? formatReasonWithAttachments(args.reason, msg.attachments) : undefined; + const reason = args.reason ? formatReasonWithAttachments(args.reason, [...msg.attachments.values()]) : undefined; let muteResult: MuteResult; const mutesPlugin = pluginData.getPlugin(MutesPlugin); @@ -45,7 +51,7 @@ export async function actualMuteUserCmd( try { contactMethods = readContactMethodsFromArgs(args); } catch (e) { - sendErrorMessage(pluginData, msg.channel, e.message); + sendErrorMessage(pluginData, msg.channel as TextChannel, e.message); return; } @@ -59,16 +65,16 @@ export async function actualMuteUserCmd( }); } catch (e) { if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) { - sendErrorMessage(pluginData, msg.channel, "Could not mute the user: no mute role set in config"); - } else if (isDiscordRESTError(e) && e.code === 10007) { - sendErrorMessage(pluginData, msg.channel, "Could not mute the user: unknown member"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "Could not mute the user: no mute role set in config"); + } else if (isDiscordAPIError(e) && e.code === 10007) { + sendErrorMessage(pluginData, msg.channel as TextChannel, "Could not mute the user: unknown member"); } else { logger.error(`Failed to mute user ${user.id}: ${e.stack}`); if (user.id == null) { // tslint-disable-next-line:no-console console.trace("[DEBUG] Null user.id for mute"); } - sendErrorMessage(pluginData, msg.channel, "Could not mute the user"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "Could not mute the user"); } return; @@ -79,29 +85,29 @@ export async function actualMuteUserCmd( if (args.time) { if (muteResult.updatedExistingMute) { response = asSingleLine(` - Updated **${user.username}#${user.discriminator}**'s + Updated **${user.tag}**'s mute to ${timeUntilUnmute} (Case #${muteResult.case.case_number}) `); } else { response = asSingleLine(` - Muted **${user.username}#${user.discriminator}** + Muted **${user.tag}** for ${timeUntilUnmute} (Case #${muteResult.case.case_number}) `); } } else { if (muteResult.updatedExistingMute) { response = asSingleLine(` - Updated **${user.username}#${user.discriminator}**'s + Updated **${user.tag}**'s mute to indefinite (Case #${muteResult.case.case_number}) `); } else { response = asSingleLine(` - Muted **${user.username}#${user.discriminator}** + Muted **${user.tag}** indefinitely (Case #${muteResult.case.case_number}) `); } } if (muteResult.notifyResult.text) response += ` (${muteResult.notifyResult.text})`; - sendSuccessMessage(pluginData, msg.channel, response); + sendSuccessMessage(pluginData, msg.channel as TextChannel, response); } diff --git a/backend/src/plugins/ModActions/functions/actualUnmuteUserCmd.ts b/backend/src/plugins/ModActions/functions/actualUnmuteUserCmd.ts index 41a8b1a1..46ab7be6 100644 --- a/backend/src/plugins/ModActions/functions/actualUnmuteUserCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualUnmuteUserCmd.ts @@ -1,17 +1,17 @@ -import { GuildPluginData } from "knub"; -import { ModActionsPluginType } from "../types"; -import { User, Message, Member } from "eris"; -import { UnknownUser, asSingleLine } from "../../../utils"; -import { sendErrorMessage, sendSuccessMessage, hasPermission } from "../../../pluginUtils"; -import { formatReasonWithAttachments } from "./formatReasonWithAttachments"; -import { MutesPlugin } from "../../../plugins/Mutes/MutesPlugin"; +import { GuildMember, Message, TextChannel, User } from "discord.js"; import humanizeDuration from "humanize-duration"; +import { GuildPluginData } from "knub"; +import { MutesPlugin } from "../../../plugins/Mutes/MutesPlugin"; +import { hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { asSingleLine, UnknownUser } from "../../../utils"; +import { ModActionsPluginType } from "../types"; +import { formatReasonWithAttachments } from "./formatReasonWithAttachments"; export async function actualUnmuteCmd( pluginData: GuildPluginData, user: User | UnknownUser, msg: Message, - args: { time?: number; reason?: string; mod?: Member }, + args: { time?: number; reason?: string; mod?: GuildMember }, ) { // The moderator who did the action is the message author or, if used, the specified -mod let mod = msg.author; @@ -19,7 +19,7 @@ export async function actualUnmuteCmd( if (args.mod) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id }))) { - sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "You don't have permission to use -mod"); return; } @@ -27,7 +27,7 @@ export async function actualUnmuteCmd( pp = msg.author; } - const reason = args.reason ? formatReasonWithAttachments(args.reason, msg.attachments) : undefined; + const reason = args.reason ? formatReasonWithAttachments(args.reason, [...msg.attachments.values()]) : undefined; const mutesPlugin = pluginData.getPlugin(MutesPlugin); const result = await mutesPlugin.unmuteUser(user.id, args.time, { @@ -37,7 +37,7 @@ export async function actualUnmuteCmd( }); if (!result) { - sendErrorMessage(pluginData, msg.channel, "User is not muted!"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "User is not muted!"); return; } @@ -46,18 +46,18 @@ export async function actualUnmuteCmd( const timeUntilUnmute = args.time && humanizeDuration(args.time); sendSuccessMessage( pluginData, - msg.channel, + msg.channel as TextChannel, asSingleLine(` - Unmuting **${user.username}#${user.discriminator}** + Unmuting **${user.tag}** in ${timeUntilUnmute} (Case #${result.case.case_number}) `), ); } else { sendSuccessMessage( pluginData, - msg.channel, + msg.channel as TextChannel, asSingleLine(` - Unmuted **${user.username}#${user.discriminator}** + Unmuted **${user.tag}** (Case #${result.case.case_number}) `), ); diff --git a/backend/src/plugins/ModActions/functions/banUserId.ts b/backend/src/plugins/ModActions/functions/banUserId.ts index 709ba994..3462842d 100644 --- a/backend/src/plugins/ModActions/functions/banUserId.ts +++ b/backend/src/plugins/ModActions/functions/banUserId.ts @@ -1,5 +1,11 @@ +import { DiscordAPIError, Snowflake, User } from "discord.js"; +import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; -import { BanOptions, BanResult, IgnoredEventType, ModActionsPluginType } from "../types"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { CaseTypes } from "../../../data/CaseTypes"; +import { LogType } from "../../../data/LogType"; +import { logger } from "../../../logger"; +import { renderTemplate } from "../../../templateFormatter"; import { createUserNotificationError, notifyUser, @@ -8,15 +14,10 @@ import { ucfirst, UserNotificationResult, } from "../../../utils"; -import { DiscordRESTError, User } from "eris"; -import { renderTemplate } from "../../../templateFormatter"; -import { getDefaultContactMethods } from "./getDefaultContactMethods"; -import { LogType } from "../../../data/LogType"; -import { ignoreEvent } from "./ignoreEvent"; import { CasesPlugin } from "../../Cases/CasesPlugin"; -import { CaseTypes } from "../../../data/CaseTypes"; -import { logger } from "../../../logger"; -import humanizeDuration from "humanize-duration"; +import { BanOptions, BanResult, IgnoredEventType, ModActionsPluginType } from "../types"; +import { getDefaultContactMethods } from "./getDefaultContactMethods"; +import { ignoreEvent } from "./ignoreEvent"; /** * Ban the specified user id, whether or not they're actually on the server at the time. Generates a case. @@ -77,14 +78,13 @@ export async function banUserId( ignoreEvent(pluginData, IgnoredEventType.Ban, userId); try { const deleteMessageDays = Math.min(30, Math.max(0, banOptions.deleteMessageDays ?? 1)); - await pluginData.guild.banMember( - userId, - deleteMessageDays, - reason != null ? encodeURIComponent(reason) : undefined, - ); + await pluginData.guild.bans.create(userId as Snowflake, { + days: deleteMessageDays, + reason: reason ?? undefined, + }); } catch (e) { let errorMessage; - if (e instanceof DiscordRESTError) { + if (e instanceof DiscordAPIError) { errorMessage = `API error ${e.code}: ${e.message}`; } else { logger.warn(`Error applying ban to ${userId}: ${e}`); @@ -99,7 +99,7 @@ export async function banUserId( const existingTempban = await pluginData.state.tempbans.findExistingTempbanForUserId(user.id); if (banTime && banTime > 0) { - const selfId = pluginData.client.user.id; + const selfId = pluginData.client.user!.id; if (existingTempban) { pluginData.state.tempbans.updateExpiryTime(user.id, banTime, banOptions.modId ?? selfId); } else { @@ -108,7 +108,7 @@ export async function banUserId( } // Create a case for this action - const modId = banOptions.caseArgs?.modId || pluginData.client.user.id; + const modId = banOptions.caseArgs?.modId || pluginData.client.user!.id; const casesPlugin = pluginData.getPlugin(CasesPlugin); const noteDetails: string[] = []; @@ -130,8 +130,8 @@ export async function banUserId( const mod = await resolveUser(pluginData.client, modId); const logtype = banTime ? LogType.MEMBER_TIMED_BAN : LogType.MEMBER_BAN; pluginData.state.serverLogs.log(logtype, { - mod: stripObjectToScalars(mod), - user: stripObjectToScalars(user), + mod: userToConfigAccessibleUser(mod), + user: userToConfigAccessibleUser(user), caseNumber: createdCase.case_number, reason, banTime: banTime ? humanizeDuration(banTime) : null, diff --git a/backend/src/plugins/ModActions/functions/formatReasonWithAttachments.ts b/backend/src/plugins/ModActions/functions/formatReasonWithAttachments.ts index d8a6b4e4..809fe909 100644 --- a/backend/src/plugins/ModActions/functions/formatReasonWithAttachments.ts +++ b/backend/src/plugins/ModActions/functions/formatReasonWithAttachments.ts @@ -1,6 +1,6 @@ -import { Attachment } from "eris"; +import { MessageAttachment } from "discord.js"; -export function formatReasonWithAttachments(reason: string, attachments: Attachment[]) { +export function formatReasonWithAttachments(reason: string, attachments: MessageAttachment[]) { const attachmentUrls = attachments.map(a => a.url); return ((reason || "") + " " + attachmentUrls.join(" ")).trim(); } diff --git a/backend/src/plugins/ModActions/functions/getDefaultContactMethods.ts b/backend/src/plugins/ModActions/functions/getDefaultContactMethods.ts index 4ebc4d8d..a94a848a 100644 --- a/backend/src/plugins/ModActions/functions/getDefaultContactMethods.ts +++ b/backend/src/plugins/ModActions/functions/getDefaultContactMethods.ts @@ -1,7 +1,7 @@ +import { Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { ModActionsPluginType } from "../types"; import { UserNotificationMethod } from "../../../utils"; -import { TextChannel } from "eris"; +import { ModActionsPluginType } from "../types"; export function getDefaultContactMethods( pluginData: GuildPluginData, @@ -15,7 +15,7 @@ export function getDefaultContactMethods( } if (config[`message_on_${type}`] && config.message_channel) { - const channel = pluginData.guild.channels.get(config.message_channel); + const channel = pluginData.guild.channels.cache.get(config.message_channel as Snowflake); if (channel instanceof TextChannel) { methods.push({ type: "channel", diff --git a/backend/src/plugins/ModActions/functions/ignoreEvent.ts b/backend/src/plugins/ModActions/functions/ignoreEvent.ts index 6f97e84f..71c25362 100644 --- a/backend/src/plugins/ModActions/functions/ignoreEvent.ts +++ b/backend/src/plugins/ModActions/functions/ignoreEvent.ts @@ -1,6 +1,6 @@ import { GuildPluginData } from "knub"; -import { IgnoredEventType, ModActionsPluginType } from "../types"; import { SECONDS } from "../../../utils"; +import { IgnoredEventType, ModActionsPluginType } from "../types"; import { clearIgnoredEvents } from "./clearIgnoredEvents"; const DEFAULT_TIMEOUT = 15 * SECONDS; diff --git a/backend/src/plugins/ModActions/functions/isBanned.ts b/backend/src/plugins/ModActions/functions/isBanned.ts index 7bcd22d8..09ec1e2e 100644 --- a/backend/src/plugins/ModActions/functions/isBanned.ts +++ b/backend/src/plugins/ModActions/functions/isBanned.ts @@ -1,18 +1,18 @@ +import { Permissions, Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; -import { ModActionsPluginType } from "../types"; -import { isDiscordHTTPError, isDiscordRESTError, SECONDS, sleep } from "../../../utils"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; import { LogType } from "../../../data/LogType"; +import { isDiscordAPIError, isDiscordHTTPError, SECONDS, sleep } from "../../../utils"; import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions"; -import { Constants } from "eris"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { ModActionsPluginType } from "../types"; export async function isBanned( pluginData: GuildPluginData, userId: string, timeout: number = 5 * SECONDS, ): Promise { - const botMember = pluginData.guild.members.get(pluginData.client.user.id); - if (botMember && !hasDiscordPermissions(botMember.permissions, Constants.Permissions.banMembers)) { + const botMember = pluginData.guild.members.cache.get(pluginData.client.user!.id); + if (botMember && !hasDiscordPermissions(botMember.permissions, Permissions.FLAGS.BAN_MEMBERS)) { pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, { body: `Missing "Ban Members" permission to check for existing bans`, }); @@ -20,10 +20,13 @@ export async function isBanned( } try { - const potentialBan = await Promise.race([pluginData.guild.getBan(userId), sleep(timeout)]); + const potentialBan = await Promise.race([ + pluginData.guild.bans.fetch({ user: userId as Snowflake }).catch(() => null), + sleep(timeout), + ]); return potentialBan != null; } catch (e) { - if (isDiscordRESTError(e) && e.code === 10026) { + if (isDiscordAPIError(e) && e.code === 10026) { // [10026]: Unknown Ban return false; } @@ -33,7 +36,7 @@ export async function isBanned( return false; } - if (isDiscordRESTError(e) && e.code === 50013) { + if (isDiscordAPIError(e) && e.code === 50013) { pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, { body: `Missing "Ban Members" permission to check for existing bans`, }); diff --git a/backend/src/plugins/ModActions/functions/kickMember.ts b/backend/src/plugins/ModActions/functions/kickMember.ts index b682018e..3ffcab6b 100644 --- a/backend/src/plugins/ModActions/functions/kickMember.ts +++ b/backend/src/plugins/ModActions/functions/kickMember.ts @@ -1,27 +1,21 @@ +import { GuildMember } from "discord.js"; import { GuildPluginData } from "knub"; -import { IgnoredEventType, KickOptions, KickResult, ModActionsPluginType } from "../types"; -import { Member } from "eris"; -import { - createUserNotificationError, - notifyUser, - resolveUser, - stripObjectToScalars, - ucfirst, - UserNotificationResult, -} from "../../../utils"; -import { renderTemplate } from "../../../templateFormatter"; -import { getDefaultContactMethods } from "./getDefaultContactMethods"; -import { LogType } from "../../../data/LogType"; -import { ignoreEvent } from "./ignoreEvent"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { CaseTypes } from "../../../data/CaseTypes"; +import { LogType } from "../../../data/LogType"; +import { renderTemplate } from "../../../templateFormatter"; +import { createUserNotificationError, notifyUser, resolveUser, ucfirst, UserNotificationResult } from "../../../utils"; import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { IgnoredEventType, KickOptions, KickResult, ModActionsPluginType } from "../types"; +import { getDefaultContactMethods } from "./getDefaultContactMethods"; +import { ignoreEvent } from "./ignoreEvent"; /** * Kick the specified server member. Generates a case. */ export async function kickMember( pluginData: GuildPluginData, - member: Member, + member: GuildMember, reason?: string, kickOptions: KickOptions = {}, ): Promise { @@ -40,7 +34,7 @@ export async function kickMember( guildName: pluginData.guild.name, reason, moderator: kickOptions.caseArgs?.modId - ? stripObjectToScalars(await resolveUser(pluginData.client, kickOptions.caseArgs.modId)) + ? userToConfigAccessibleUser(await resolveUser(pluginData.client, kickOptions.caseArgs.modId)) : {}, }); @@ -55,7 +49,7 @@ export async function kickMember( pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_KICK, member.id); ignoreEvent(pluginData, IgnoredEventType.Kick, member.id); try { - await member.kick(reason != null ? encodeURIComponent(reason) : undefined); + await member.kick(reason ?? undefined); } catch (e) { return { status: "failed", @@ -63,7 +57,7 @@ export async function kickMember( }; } - const modId = kickOptions.caseArgs?.modId || pluginData.client.user.id; + const modId = kickOptions.caseArgs?.modId || pluginData.client.user!.id; // Create a case for this action const casesPlugin = pluginData.getPlugin(CasesPlugin); @@ -79,8 +73,8 @@ export async function kickMember( // Log the action const mod = await resolveUser(pluginData.client, modId); pluginData.state.serverLogs.log(LogType.MEMBER_KICK, { - mod: stripObjectToScalars(mod), - user: stripObjectToScalars(member.user), + mod: userToConfigAccessibleUser(mod), + user: userToConfigAccessibleUser(member.user), caseNumber: createdCase.case_number, reason, }); diff --git a/backend/src/plugins/ModActions/functions/outdatedTempbansLoop.ts b/backend/src/plugins/ModActions/functions/outdatedTempbansLoop.ts index 0fc3749c..935e16a4 100644 --- a/backend/src/plugins/ModActions/functions/outdatedTempbansLoop.ts +++ b/backend/src/plugins/ModActions/functions/outdatedTempbansLoop.ts @@ -1,15 +1,17 @@ -import { resolveUser, SECONDS, stripObjectToScalars } from "../../../utils"; +import { Snowflake } from "discord.js"; +import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; -import { IgnoredEventType, ModActionsPluginType } from "../types"; +import moment from "moment-timezone"; import { LogType } from "src/data/LogType"; +import { logger } from "src/logger"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { CaseTypes } from "../../../data/CaseTypes"; +import { resolveUser, SECONDS } from "../../../utils"; +import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { IgnoredEventType, ModActionsPluginType } from "../types"; import { formatReasonWithAttachments } from "./formatReasonWithAttachments"; import { ignoreEvent } from "./ignoreEvent"; import { isBanned } from "./isBanned"; -import { logger } from "src/logger"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; -import { CaseTypes } from "../../../data/CaseTypes"; -import moment from "moment-timezone"; -import humanizeDuration from "humanize-duration"; const TEMPBAN_LOOP_TIME = 60 * SECONDS; @@ -30,7 +32,7 @@ export async function outdatedTempbansLoop(pluginData: GuildPluginData, - member: Member, + member: GuildMember, reason: string, warnOptions: WarnOptions = {}, ): Promise { @@ -30,7 +24,7 @@ export async function warnMember( guildName: pluginData.guild.name, reason, moderator: warnOptions.caseArgs?.modId - ? stripObjectToScalars(await resolveUser(pluginData.client, warnOptions.caseArgs.modId)) + ? userToConfigAccessibleUser(await resolveUser(pluginData.client, warnOptions.caseArgs.modId)) : {}, }); const contactMethods = warnOptions?.contactMethods @@ -42,13 +36,14 @@ export async function warnMember( } if (!notifyResult.success) { - if (warnOptions.retryPromptChannel && pluginData.guild.channels.has(warnOptions.retryPromptChannel.id)) { - const failedMsg = await warnOptions.retryPromptChannel.createMessage( - "Failed to message the user. Log the warning anyway?", + if (warnOptions.retryPromptChannel && pluginData.guild.channels.resolve(warnOptions.retryPromptChannel.id)) { + const reply = await waitForButtonConfirm( + warnOptions.retryPromptChannel, + { content: "Failed to message the user. Log the warning anyway?" }, + { confirmText: "Yes", cancelText: "No", restrictToId: warnOptions.caseArgs?.modId }, ); - const reply = await waitForReaction(pluginData.client, failedMsg, ["✅", "❌"]); - failedMsg.delete(); - if (!reply || reply.name === "❌") { + + if (!reply) { return { status: "failed", error: "Failed to message user", @@ -62,7 +57,7 @@ export async function warnMember( } } - const modId = warnOptions.caseArgs?.modId ?? pluginData.client.user.id; + const modId = warnOptions.caseArgs?.modId ?? pluginData.client.user!.id; const casesPlugin = pluginData.getPlugin(CasesPlugin); const createdCase = await casesPlugin.createCase({ @@ -74,10 +69,10 @@ export async function warnMember( noteDetails: notifyResult.text ? [ucfirst(notifyResult.text)] : [], }); - const mod = await resolveUser(pluginData.client, modId); + const mod = await pluginData.guild.members.fetch(modId as Snowflake); pluginData.state.serverLogs.log(LogType.MEMBER_WARN, { - mod: stripObjectToScalars(mod), - member: stripObjectToScalars(member, ["user", "roles"]), + mod: memberToConfigAccessibleMember(mod), + member: memberToConfigAccessibleMember(member), caseNumber: createdCase.case_number, reason, }); diff --git a/backend/src/plugins/ModActions/types.ts b/backend/src/plugins/ModActions/types.ts index 61568a78..b8c07270 100644 --- a/backend/src/plugins/ModActions/types.ts +++ b/backend/src/plugins/ModActions/types.ts @@ -1,16 +1,17 @@ +import { TextChannel } from "discord.js"; +import { EventEmitter } from "events"; import * as t from "io-ts"; -import { tNullable, UserNotificationMethod, UserNotificationResult } from "../../utils"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub"; -import { GuildMutes } from "../../data/GuildMutes"; +import { Case } from "../../data/entities/Case"; import { GuildCases } from "../../data/GuildCases"; import { GuildLogs } from "../../data/GuildLogs"; -import { Case } from "../../data/entities/Case"; -import { CaseArgs } from "../Cases/types"; -import { TextChannel } from "eris"; +import { GuildMutes } from "../../data/GuildMutes"; import { GuildTempbans } from "../../data/GuildTempbans"; -import Timeout = NodeJS.Timeout; -import { EventEmitter } from "events"; import { Queue } from "../../Queue"; +import { tNullable, UserNotificationMethod, UserNotificationResult } from "../../utils"; +import { CaseArgs } from "../Cases/types"; + +import Timeout = NodeJS.Timeout; export const ConfigSchema = t.type({ dm_on_warn: t.boolean, diff --git a/backend/src/plugins/Mutes/MutesPlugin.ts b/backend/src/plugins/Mutes/MutesPlugin.ts index 05671350..3f0d9aae 100644 --- a/backend/src/plugins/Mutes/MutesPlugin.ts +++ b/backend/src/plugins/Mutes/MutesPlugin.ts @@ -1,25 +1,26 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { ConfigSchema, MuteOptions, MutesPluginType } from "./types"; -import { CasesPlugin } from "../Cases/CasesPlugin"; -import { GuildMutes } from "../../data/GuildMutes"; +import { GuildMember, Snowflake } from "discord.js"; +import { EventEmitter } from "events"; +import { GuildArchives } from "../../data/GuildArchives"; import { GuildCases } from "../../data/GuildCases"; import { GuildLogs } from "../../data/GuildLogs"; -import { GuildArchives } from "../../data/GuildArchives"; -import { clearExpiredMutes } from "./functions/clearExpiredMutes"; -import { MutesCmd } from "./commands/MutesCmd"; -import { ClearBannedMutesCmd } from "./commands/ClearBannedMutesCmd"; -import { ClearActiveMuteOnRoleRemovalEvt } from "./events/ClearActiveMuteOnRoleRemovalEvt"; -import { ClearMutesWithoutRoleCmd } from "./commands/ClearMutesWithoutRoleCmd"; -import { ClearMutesCmd } from "./commands/ClearMutesCmd"; -import { muteUser } from "./functions/muteUser"; -import { unmuteUser } from "./functions/unmuteUser"; -import { Member } from "eris"; -import { ClearActiveMuteOnMemberBanEvt } from "./events/ClearActiveMuteOnMemberBanEvt"; -import { ReapplyActiveMuteOnJoinEvt } from "./events/ReapplyActiveMuteOnJoinEvt"; +import { GuildMutes } from "../../data/GuildMutes"; import { mapToPublicFn } from "../../pluginUtils"; -import { EventEmitter } from "events"; -import { onMutesEvent } from "./functions/onMutesEvent"; +import { CasesPlugin } from "../Cases/CasesPlugin"; +import { LogsPlugin } from "../Logs/LogsPlugin"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import { ClearBannedMutesCmd } from "./commands/ClearBannedMutesCmd"; +import { ClearMutesCmd } from "./commands/ClearMutesCmd"; +import { ClearMutesWithoutRoleCmd } from "./commands/ClearMutesWithoutRoleCmd"; +import { MutesCmd } from "./commands/MutesCmd"; +import { ClearActiveMuteOnMemberBanEvt } from "./events/ClearActiveMuteOnMemberBanEvt"; +import { ClearActiveMuteOnRoleRemovalEvt } from "./events/ClearActiveMuteOnRoleRemovalEvt"; +import { ReapplyActiveMuteOnJoinEvt } from "./events/ReapplyActiveMuteOnJoinEvt"; +import { clearExpiredMutes } from "./functions/clearExpiredMutes"; +import { muteUser } from "./functions/muteUser"; import { offMutesEvent } from "./functions/offMutesEvent"; +import { onMutesEvent } from "./functions/onMutesEvent"; +import { unmuteUser } from "./functions/unmuteUser"; +import { ConfigSchema, MutesPluginType } from "./types"; const defaultOptions = { config: { @@ -71,7 +72,7 @@ export const MutesPlugin = zeppelinGuildPlugin()({ configSchema: ConfigSchema, defaultOptions, - dependencies: [CasesPlugin], + dependencies: [CasesPlugin, LogsPlugin], // prettier-ignore commands: [ @@ -92,9 +93,9 @@ export const MutesPlugin = zeppelinGuildPlugin()({ muteUser: mapToPublicFn(muteUser), unmuteUser: mapToPublicFn(unmuteUser), hasMutedRole(pluginData) { - return (member: Member) => { + return (member: GuildMember) => { const muteRole = pluginData.config.get().mute_role; - return muteRole ? member.roles.includes(muteRole) : false; + return muteRole ? member.roles.cache.has(muteRole as Snowflake) : false; }; }, diff --git a/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts b/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts index b59f1165..ad5b9666 100644 --- a/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts +++ b/backend/src/plugins/Mutes/commands/ClearBannedMutesCmd.ts @@ -1,6 +1,6 @@ -import { mutesCmd } from "../types"; -import { User } from "eris"; +import { Snowflake, User } from "discord.js"; import { sendSuccessMessage } from "../../../pluginUtils"; +import { mutesCmd } from "../types"; export const ClearBannedMutesCmd = mutesCmd({ trigger: "clear_banned_mutes", @@ -8,21 +8,18 @@ export const ClearBannedMutesCmd = mutesCmd({ description: "Clear dangling mutes for members who have been banned", async run({ pluginData, message: msg }) { - await msg.channel.createMessage("Clearing mutes from banned users..."); + await msg.channel.send("Clearing mutes from banned users..."); const activeMutes = await pluginData.state.mutes.getActiveMutes(); - // Mismatch in Eris docs and actual result here, based on Eris's code comments anyway - const bans: Array<{ reason: string; user: User }> = (await pluginData.guild.getBans()) as any; + const bans = await pluginData.guild.bans.fetch({ cache: true }); const bannedIds = bans.map(b => b.user.id); - await msg.channel.createMessage( - `Found ${activeMutes.length} mutes and ${bannedIds.length} bans, cross-referencing...`, - ); + await msg.channel.send(`Found ${activeMutes.length} mutes and ${bannedIds.length} bans, cross-referencing...`); let cleared = 0; for (const mute of activeMutes) { - if (bannedIds.includes(mute.user_id)) { + if (bannedIds.includes(mute.user_id as Snowflake)) { await pluginData.state.mutes.clear(mute.user_id); cleared++; } diff --git a/backend/src/plugins/Mutes/commands/ClearMutesCmd.ts b/backend/src/plugins/Mutes/commands/ClearMutesCmd.ts index 7a07efb7..3021a7a7 100644 --- a/backend/src/plugins/Mutes/commands/ClearMutesCmd.ts +++ b/backend/src/plugins/Mutes/commands/ClearMutesCmd.ts @@ -1,6 +1,6 @@ -import { mutesCmd } from "../types"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { mutesCmd } from "../types"; export const ClearMutesCmd = mutesCmd({ trigger: "clear_mutes", diff --git a/backend/src/plugins/Mutes/commands/ClearMutesWithoutRoleCmd.ts b/backend/src/plugins/Mutes/commands/ClearMutesWithoutRoleCmd.ts index f05b16cf..0dbef291 100644 --- a/backend/src/plugins/Mutes/commands/ClearMutesWithoutRoleCmd.ts +++ b/backend/src/plugins/Mutes/commands/ClearMutesWithoutRoleCmd.ts @@ -1,6 +1,7 @@ -import { mutesCmd } from "../types"; +import { Snowflake } from "discord.js"; import { sendSuccessMessage } from "../../../pluginUtils"; import { resolveMember } from "../../../utils"; +import { mutesCmd } from "../types"; export const ClearMutesWithoutRoleCmd = mutesCmd({ trigger: "clear_mutes_without_role", @@ -12,14 +13,14 @@ export const ClearMutesWithoutRoleCmd = mutesCmd({ const muteRole = pluginData.config.get().mute_role; if (!muteRole) return; - await msg.channel.createMessage("Clearing mutes from members that don't have the mute role..."); + await msg.channel.send("Clearing mutes from members that don't have the mute role..."); let cleared = 0; for (const mute of activeMutes) { const member = await resolveMember(pluginData.client, pluginData.guild, mute.user_id); if (!member) continue; - if (!member.roles.includes(muteRole)) { + if (!member.roles.cache.has(muteRole as Snowflake)) { await pluginData.state.mutes.clear(mute.user_id); cleared++; } diff --git a/backend/src/plugins/Mutes/commands/MutesCmd.ts b/backend/src/plugins/Mutes/commands/MutesCmd.ts index 9636141a..99356e54 100644 --- a/backend/src/plugins/Mutes/commands/MutesCmd.ts +++ b/backend/src/plugins/Mutes/commands/MutesCmd.ts @@ -1,10 +1,10 @@ -import { IMuteWithDetails, mutesCmd } from "../types"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { DBDateFormat, isFullMessage, MINUTES, noop, resolveMember } from "../../../utils"; +import { GuildMember, MessageActionRow, MessageButton, MessageComponentInteraction, Snowflake } from "discord.js"; import moment from "moment-timezone"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; import { humanizeDurationShort } from "../../../humanizeDurationShort"; import { getBaseUrl } from "../../../pluginUtils"; -import { Member } from "eris"; +import { DBDateFormat, MINUTES, resolveMember } from "../../../utils"; +import { IMuteWithDetails, mutesCmd } from "../types"; export const MutesCmd = mutesCmd({ trigger: "mutes", @@ -16,21 +16,27 @@ export const MutesCmd = mutesCmd({ shortcut: "a", }), - left: ct.switchOption({ shortcut: "l" }), - manual: ct.switchOption({ shortcut: "m" }), - export: ct.switchOption({ shortcut: "e" }), + left: ct.switchOption({ def: false, shortcut: "l" }), + manual: ct.switchOption({ def: false, shortcut: "m" }), + export: ct.switchOption({ def: false, shortcut: "e" }), }, async run({ pluginData, message: msg, args }) { - const listMessagePromise = msg.channel.createMessage("Loading mutes..."); + const listMessagePromise = msg.channel.send("Loading mutes..."); const mutesPerPage = 10; let totalMutes = 0; let hasFilters = false; - let hasReactions = false; - let clearReactionsFn; - let clearReactionsTimeout; - const clearReactionsDebounce = 5 * MINUTES; + let stopCollectionFn = () => { + return; + }; + let stopCollectionTimeout: NodeJS.Timeout; + const stopCollectionDebounce = 5 * MINUTES; + + const bumpCollectionTimeout = () => { + clearTimeout(stopCollectionTimeout); + stopCollectionTimeout = setTimeout(stopCollectionFn, stopCollectionDebounce); + }; let lines: string[] = []; @@ -48,20 +54,20 @@ export const MutesCmd = mutesCmd({ if (args.manual) { // Show only manual mutes (i.e. "Muted" role added without a logged mute) const muteUserIds = new Set(activeMutes.map(m => m.user_id)); - const manuallyMutedMembers: Member[] = []; + const manuallyMutedMembers: GuildMember[] = []; const muteRole = pluginData.config.get().mute_role; if (muteRole) { - pluginData.guild.members.forEach(member => { + pluginData.guild.members.cache.forEach(member => { if (muteUserIds.has(member.id)) return; - if (member.roles.includes(muteRole)) manuallyMutedMembers.push(member); + if (member.roles.cache.has(muteRole as Snowflake)) manuallyMutedMembers.push(member); }); } totalMutes = manuallyMutedMembers.length; lines = manuallyMutedMembers.map(member => { - return `<@!${member.id}> (**${member.user.username}#${member.user.discriminator}**, \`${member.id}\`) 🔧 Manual mute`; + return `<@!${member.id}> (**${member.user.tag}**, \`${member.id}\`) 🔧 Manual mute`; }); } else { // Show filtered active mutes (but not manual mutes) @@ -86,7 +92,7 @@ export const MutesCmd = mutesCmd({ if (!member) { if (!bannedIds) { - const bans = await pluginData.guild.getBans(); + const bans = await pluginData.guild.bans.fetch({ cache: true }); bannedIds = bans.map(u => u.user.id); } @@ -112,8 +118,8 @@ export const MutesCmd = mutesCmd({ const muteCasesById = muteCases.reduce((map, c) => map.set(c.id, c), new Map()); lines = filteredMutes.map(mute => { - const user = pluginData.client.users.get(mute.user_id); - const username = user ? `${user.username}#${user.discriminator}` : "Unknown#0000"; + const user = pluginData.client.users.resolve(mute.user_id as Snowflake); + const username = user ? user.tag : "Unknown#0000"; const theCase = muteCasesById.get(mute.case_id); const caseName = theCase ? `Case #${theCase.case_number}` : "No case"; @@ -167,13 +173,7 @@ export const MutesCmd = mutesCmd({ message += "\n\n" + pageLines.join("\n"); listMessage.edit(message); - bumpClearReactionsTimeout(); - }; - - const bumpClearReactionsTimeout = () => { - if (!hasReactions) return; - clearTimeout(clearReactionsTimeout); - clearReactionsTimeout = setTimeout(clearReactionsFn, clearReactionsDebounce); + bumpCollectionTimeout(); }; if (totalMutes === 0) { @@ -194,33 +194,47 @@ export const MutesCmd = mutesCmd({ drawListPage(1); if (totalPages > 1) { - hasReactions = true; - listMessage.addReaction("⬅"); - listMessage.addReaction("➡"); + const idMod = `${listMessage.id}:muteList`; + const buttons: MessageButton[] = []; - const paginationReactionListener = pluginData.events.on( - "messageReactionAdd", - ({ args: { message: rMsg, emoji, member } }) => { - if (!isFullMessage(rMsg)) return; - if (rMsg.id !== listMessage.id) return; - if (member.id !== msg.author.id) return; - if (!["⬅", "➡"].includes(emoji.name)) return; - - if (emoji.name === "⬅" && currentPage > 1) { - drawListPage(currentPage - 1); - } else if (emoji.name === "➡" && currentPage < totalPages) { - drawListPage(currentPage + 1); - } - - rMsg.removeReaction(emoji.name, member.id).catch(noop); - }, + buttons.push( + new MessageButton() + .setStyle("SECONDARY") + .setEmoji("⬅") + .setCustomId(`previousButton:${idMod}`), ); - clearReactionsFn = () => { - listMessage.removeReactions().catch(noop); - pluginData.events.off("messageReactionAdd", paginationReactionListener); + buttons.push( + new MessageButton() + .setStyle("SECONDARY") + .setEmoji("➡") + .setCustomId(`nextButton:${idMod}`), + ); + + const row = new MessageActionRow().addComponents(buttons); + await listMessage.edit({ components: [row] }); + + const collector = listMessage.createMessageComponentCollector({ time: stopCollectionDebounce }); + + collector.on("collect", async (interaction: MessageComponentInteraction) => { + if (msg.author.id !== interaction.user.id) { + interaction.reply({ content: `You are not permitted to use these buttons.`, ephemeral: true }); + } else { + collector.resetTimer(); + await interaction.deferUpdate(); + if (interaction.customId === `previousButton:${idMod}` && currentPage > 1) { + await drawListPage(currentPage - 1); + } else if (interaction.customId === `nextButton:${idMod}` && currentPage < totalPages) { + await drawListPage(currentPage + 1); + } + } + }); + + stopCollectionFn = async () => { + collector.stop(); + await listMessage.edit({ content: listMessage.content, components: [] }); }; - bumpClearReactionsTimeout(); + bumpCollectionTimeout(); } } }, diff --git a/backend/src/plugins/Mutes/events/ClearActiveMuteOnMemberBanEvt.ts b/backend/src/plugins/Mutes/events/ClearActiveMuteOnMemberBanEvt.ts index 7a40b25e..94604dcd 100644 --- a/backend/src/plugins/Mutes/events/ClearActiveMuteOnMemberBanEvt.ts +++ b/backend/src/plugins/Mutes/events/ClearActiveMuteOnMemberBanEvt.ts @@ -5,10 +5,10 @@ import { mutesEvt } from "../types"; */ export const ClearActiveMuteOnMemberBanEvt = mutesEvt({ event: "guildBanAdd", - async listener({ pluginData, args: { user } }) { - const mute = await pluginData.state.mutes.findExistingMuteForUserId(user.id); + async listener({ pluginData, args: { ban } }) { + const mute = await pluginData.state.mutes.findExistingMuteForUserId(ban.user.id); if (mute) { - pluginData.state.mutes.clear(user.id); + pluginData.state.mutes.clear(ban.user.id); } }, }); diff --git a/backend/src/plugins/Mutes/events/ClearActiveMuteOnRoleRemovalEvt.ts b/backend/src/plugins/Mutes/events/ClearActiveMuteOnRoleRemovalEvt.ts index cba7f2a2..4b8aa784 100644 --- a/backend/src/plugins/Mutes/events/ClearActiveMuteOnRoleRemovalEvt.ts +++ b/backend/src/plugins/Mutes/events/ClearActiveMuteOnRoleRemovalEvt.ts @@ -1,12 +1,12 @@ -import { mutesEvt } from "../types"; import { memberHasMutedRole } from "../functions/memberHasMutedRole"; +import { mutesEvt } from "../types"; /** * Clear active mute if the mute role is removed manually */ export const ClearActiveMuteOnRoleRemovalEvt = mutesEvt({ event: "guildMemberUpdate", - async listener({ pluginData, args: { member } }) { + async listener({ pluginData, args: { oldMember, newMember: member } }) { const muteRole = pluginData.config.get().mute_role; if (!muteRole) return; diff --git a/backend/src/plugins/Mutes/events/ReapplyActiveMuteOnJoinEvt.ts b/backend/src/plugins/Mutes/events/ReapplyActiveMuteOnJoinEvt.ts index 2b149eb7..7d4ece2d 100644 --- a/backend/src/plugins/Mutes/events/ReapplyActiveMuteOnJoinEvt.ts +++ b/backend/src/plugins/Mutes/events/ReapplyActiveMuteOnJoinEvt.ts @@ -1,7 +1,8 @@ -import { mutesEvt } from "../types"; +import { Snowflake } from "discord.js"; +import { memberToConfigAccessibleMember } from "../../../utils/configAccessibleObjects"; import { LogType } from "../../../data/LogType"; -import { stripObjectToScalars } from "../../../utils"; import { memberRolesLock } from "../../../utils/lockNameHelpers"; +import { mutesEvt } from "../types"; /** * Reapply active mutes on join @@ -15,12 +16,12 @@ export const ReapplyActiveMuteOnJoinEvt = mutesEvt({ if (muteRole) { const memberRoleLock = await pluginData.locks.acquire(memberRolesLock(member)); - await member.addRole(muteRole); + await member.roles.add(muteRole as Snowflake); memberRoleLock.unlock(); } pluginData.state.serverLogs.log(LogType.MEMBER_MUTE_REJOIN, { - member: stripObjectToScalars(member, ["user", "roles"]), + member: memberToConfigAccessibleMember(member), }); } }, diff --git a/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts b/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts index 60395d02..d812dd9d 100644 --- a/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts +++ b/backend/src/plugins/Mutes/functions/clearExpiredMutes.ts @@ -1,9 +1,10 @@ +import { Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; -import { MutesPluginType } from "../types"; +import { memberToConfigAccessibleMember } from "../../../utils/configAccessibleObjects"; import { LogType } from "../../../data/LogType"; -import { resolveMember, stripObjectToScalars, UnknownUser } from "../../../utils"; -import { MemberOptions } from "eris"; +import { resolveMember, UnknownUser } from "../../../utils"; import { memberRolesLock } from "../../../utils/lockNameHelpers"; +import { MutesPluginType } from "../types"; export async function clearExpiredMutes(pluginData: GuildPluginData) { const expiredMutes = await pluginData.state.mutes.getExpiredMutes(); @@ -16,24 +17,24 @@ export async function clearExpiredMutes(pluginData: GuildPluginData r !== muteRole); + await member.roles.remove(muteRole as Snowflake); } if (mute.roles_to_restore) { - const memberOptions: MemberOptions = {}; - const guildRoles = pluginData.guild.roles; - memberOptions.roles = Array.from( - new Set([...mute.roles_to_restore, ...member.roles.filter(x => x !== muteRole && guildRoles.has(x))]), - ); - await member.edit(memberOptions); - member.roles = memberOptions.roles; + const guildRoles = pluginData.guild.roles.cache; + let newRoles = [...member.roles.cache.keys()]; + newRoles = + muteRole && newRoles.includes(muteRole) ? newRoles.splice(newRoles.indexOf(muteRole), 1) : newRoles; + for (const toRestore of mute.roles_to_restore) { + if (guildRoles.has(toRestore as Snowflake) && toRestore !== muteRole) newRoles.push(toRestore); + } + await member.roles.set(newRoles as Snowflake[]); } lock.unlock(); } catch { pluginData.state.serverLogs.log(LogType.BOT_ALERT, { body: `Failed to remove mute role from {userMention(member)}`, - member: stripObjectToScalars(member), + member: memberToConfigAccessibleMember(member), }); } } @@ -42,7 +43,7 @@ export async function clearExpiredMutes(pluginData: GuildPluginData, member: Member): boolean { +export function memberHasMutedRole(pluginData: GuildPluginData, member: GuildMember): boolean { const muteRole = pluginData.config.get().mute_role; - return muteRole ? member.roles.includes(muteRole) : false; + return muteRole ? member.roles.cache.has(muteRole as Snowflake) : false; } diff --git a/backend/src/plugins/Mutes/functions/muteUser.ts b/backend/src/plugins/Mutes/functions/muteUser.ts index 046568b2..88001f44 100644 --- a/backend/src/plugins/Mutes/functions/muteUser.ts +++ b/backend/src/plugins/Mutes/functions/muteUser.ts @@ -1,24 +1,24 @@ -import { GuildPluginData } from "knub"; -import { MuteOptions, MutesPluginType } from "../types"; -import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; +import { Snowflake, TextChannel, User } from "discord.js"; import humanizeDuration from "humanize-duration"; +import { GuildPluginData } from "knub"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { CaseTypes } from "../../../data/CaseTypes"; +import { Case } from "../../../data/entities/Case"; +import { LogType } from "../../../data/LogType"; +import { LogsPlugin } from "../../../plugins/Logs/LogsPlugin"; +import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; +import { renderTemplate } from "../../../templateFormatter"; import { notifyUser, - resolveUser, - stripObjectToScalars, - ucfirst, - UserNotificationResult, resolveMember, + resolveUser, + ucfirst, UserNotificationMethod, + UserNotificationResult, } from "../../../utils"; -import { renderTemplate } from "../../../templateFormatter"; -import { MemberOptions, TextChannel, User } from "eris"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; -import { CaseTypes } from "../../../data/CaseTypes"; -import { LogType } from "../../../data/LogType"; -import { Case } from "../../../data/entities/Case"; -import { LogsPlugin } from "../../../plugins/Logs/LogsPlugin"; import { muteLock } from "../../../utils/lockNameHelpers"; +import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { MuteOptions, MutesPluginType } from "../types"; export async function muteUser( pluginData: GuildPluginData, @@ -42,7 +42,7 @@ export async function muteUser( // No mod specified -> mark Zeppelin as the mod if (!muteOptions.caseArgs?.modId) { muteOptions.caseArgs = muteOptions.caseArgs ?? {}; - muteOptions.caseArgs.modId = pluginData.client.user.id; + muteOptions.caseArgs.modId = pluginData.client.user!.id; } const user = await resolveUser(pluginData.client, userId); @@ -58,8 +58,8 @@ export async function muteUser( if (member) { const logs = pluginData.getPlugin(LogsPlugin); // remove and store any roles to be removed/restored - const currentUserRoles = member.roles; - const memberOptions: MemberOptions = {}; + const currentUserRoles = [...member.roles.cache.keys()]; + let newRoles: string[] = currentUserRoles; const removeRoles = removeRolesOnMuteOverride ?? config.remove_roles_on_mute; const restoreRoles = restoreRolesOnMuteOverride ?? config.restore_roles_on_mute; @@ -67,13 +67,13 @@ export async function muteUser( if (!Array.isArray(removeRoles)) { if (removeRoles) { // exclude managed roles from being removed - const managedRoles = pluginData.guild.roles.filter(x => x.managed).map(y => y.id); - memberOptions.roles = managedRoles.filter(x => member.roles.includes(x)); - await member.edit(memberOptions); + const managedRoles = pluginData.guild.roles.cache.filter(x => x.managed).map(y => y.id); + newRoles = currentUserRoles.filter(r => !managedRoles.includes(r)); + await member.roles.set(newRoles as Snowflake[]); } } else { - memberOptions.roles = currentUserRoles.filter(x => !(removeRoles).includes(x)); - await member.edit(memberOptions); + newRoles = currentUserRoles.filter(x => !(removeRoles).includes(x)); + await member.roles.set(newRoles as Snowflake[]); } // set roles to be restored @@ -86,11 +86,11 @@ export async function muteUser( } // Apply mute role if it's missing - if (!member.roles.includes(muteRole)) { + if (!currentUserRoles.includes(muteRole as Snowflake)) { try { - await member.addRole(muteRole); + await member.roles.add(muteRole as Snowflake); } catch (e) { - const actualMuteRole = pluginData.guild.roles.find(x => x.id === muteRole); + const actualMuteRole = pluginData.guild.roles.cache.get(muteRole as Snowflake); if (!actualMuteRole) { lock.unlock(); logs.log(LogType.BOT_ALERT, { @@ -99,10 +99,10 @@ export async function muteUser( throw new RecoverablePluginError(ERRORS.INVALID_MUTE_ROLE_ID); } - const zep = await resolveMember(pluginData.client, pluginData.guild, pluginData.client.user.id); - const zepRoles = pluginData.guild.roles.filter(x => zep!.roles.includes(x.id)); + const zep = await resolveMember(pluginData.client, pluginData.guild, pluginData.client.user!.id); + const zepRoles = pluginData.guild.roles.cache.filter(x => zep!.roles.cache.has(x.id)); // If we have roles and one of them is above the muted role, throw generic error - if (zepRoles.length >= 0 && zepRoles.some(zepRole => zepRole.position > actualMuteRole.position)) { + if (zepRoles.size >= 0 && zepRoles.some(zepRole => zepRole.position > actualMuteRole.position)) { lock.unlock(); logs.log(LogType.BOT_ALERT, { body: `Cannot mute user ${member.id}: ${e}`, @@ -125,7 +125,7 @@ export async function muteUser( if (moveToVoiceChannel || cfg.kick_from_voice_channel) { // TODO: Add back the voiceState check once we figure out how to get voice state for guild members that are loaded on-demand try { - await member.edit({ channelID: moveToVoiceChannel }); + await member.edit({ channel: moveToVoiceChannel as Snowflake }); } catch {} // tslint:disable-line } } @@ -156,7 +156,7 @@ export async function muteUser( reason: reason || "None", time: timeUntilUnmute, moderator: muteOptions.caseArgs?.modId - ? stripObjectToScalars(await resolveUser(pluginData.client, muteOptions.caseArgs.modId)) + ? userToConfigAccessibleUser(await resolveUser(pluginData.client, muteOptions.caseArgs.modId)) : "", })); @@ -172,7 +172,8 @@ export async function muteUser( } const useChannel = existingMute ? config.message_on_update : config.message_on_mute; - const channel = config.message_channel && pluginData.guild.channels.get(config.message_channel); + const channel = + config.message_channel && pluginData.guild.channels.cache.get(config.message_channel as Snowflake); if (useChannel && channel instanceof TextChannel) { contactMethods.push({ type: "channel", channel }); } @@ -224,16 +225,16 @@ export async function muteUser( const mod = await resolveUser(pluginData.client, muteOptions.caseArgs?.modId); if (muteTime) { pluginData.state.serverLogs.log(LogType.MEMBER_TIMED_MUTE, { - mod: stripObjectToScalars(mod), - user: stripObjectToScalars(user), + mod: userToConfigAccessibleUser(mod), + user: userToConfigAccessibleUser(user), time: timeUntilUnmute, caseNumber: theCase.case_number, reason, }); } else { pluginData.state.serverLogs.log(LogType.MEMBER_MUTE, { - mod: stripObjectToScalars(mod), - user: stripObjectToScalars(user), + mod: userToConfigAccessibleUser(mod), + user: userToConfigAccessibleUser(user), caseNumber: theCase.case_number, reason, }); diff --git a/backend/src/plugins/Mutes/functions/unmuteUser.ts b/backend/src/plugins/Mutes/functions/unmuteUser.ts index 53b7cabe..35fd1880 100644 --- a/backend/src/plugins/Mutes/functions/unmuteUser.ts +++ b/backend/src/plugins/Mutes/functions/unmuteUser.ts @@ -1,14 +1,15 @@ -import { GuildPluginData } from "knub"; -import { MutesPluginType, UnmuteResult } from "../types"; -import { CaseArgs } from "../../Cases/types"; -import { resolveUser, stripObjectToScalars, resolveMember } from "../../../utils"; -import { memberHasMutedRole } from "./memberHasMutedRole"; +import { Snowflake } from "discord.js"; import humanizeDuration from "humanize-duration"; -import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { GuildPluginData } from "knub"; +import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { CaseTypes } from "../../../data/CaseTypes"; import { LogType } from "../../../data/LogType"; -import { MemberOptions } from "eris"; +import { resolveMember, resolveUser } from "../../../utils"; import { memberRolesLock } from "../../../utils/lockNameHelpers"; +import { CasesPlugin } from "../../Cases/CasesPlugin"; +import { CaseArgs } from "../../Cases/types"; +import { MutesPluginType, UnmuteResult } from "../types"; +import { memberHasMutedRole } from "./memberHasMutedRole"; export async function unmuteUser( pluginData: GuildPluginData, @@ -19,7 +20,7 @@ export async function unmuteUser( const existingMute = await pluginData.state.mutes.findExistingMuteForUserId(userId); const user = await resolveUser(pluginData.client, userId); const member = await resolveMember(pluginData.client, pluginData.guild, userId); // Grab the fresh member so we don't have stale role info - const modId = caseArgs.modId || pluginData.client.user.id; + const modId = caseArgs.modId || pluginData.client.user!.id; if (!existingMute && member && !memberHasMutedRole(pluginData, member)) return null; @@ -36,18 +37,17 @@ export async function unmuteUser( const lock = await pluginData.locks.acquire(memberRolesLock(member)); const muteRole = pluginData.config.get().mute_role; - if (muteRole && member.roles.includes(muteRole)) { - await member.removeRole(muteRole); - member.roles = member.roles.filter(r => r !== muteRole); + if (muteRole && member.roles.cache.has(muteRole as Snowflake)) { + await member.roles.remove(muteRole as Snowflake); } if (existingMute?.roles_to_restore) { - const memberOptions: MemberOptions = {}; - const guildRoles = pluginData.guild.roles; - memberOptions.roles = Array.from( - new Set([...existingMute.roles_to_restore, ...member.roles.filter(x => x !== muteRole && guildRoles.has(x))]), - ); - await member.edit(memberOptions); - member.roles = memberOptions.roles; + const guildRoles = pluginData.guild.roles.cache; + let newRoles = [...member.roles.cache.keys()]; + newRoles = muteRole && newRoles.includes(muteRole) ? newRoles.splice(newRoles.indexOf(muteRole), 1) : newRoles; + for (const toRestore of existingMute.roles_to_restore) { + if (guildRoles.has(toRestore as Snowflake) && toRestore !== muteRole) newRoles.push(toRestore); + } + await member.roles.set(newRoles as Snowflake[]); } lock.unlock(); @@ -85,19 +85,19 @@ export async function unmuteUser( }); // Log the action - const mod = pluginData.client.users.get(modId); + const mod = await pluginData.client.users.fetch(modId as Snowflake); if (unmuteTime) { pluginData.state.serverLogs.log(LogType.MEMBER_TIMED_UNMUTE, { - mod: stripObjectToScalars(mod), - user: stripObjectToScalars(user), + mod: userToConfigAccessibleUser(mod), + user: userToConfigAccessibleUser(user), caseNumber: createdCase.case_number, time: timeUntilUnmute, reason: caseArgs.reason, }); } else { pluginData.state.serverLogs.log(LogType.MEMBER_UNMUTE, { - mod: stripObjectToScalars(mod), - user: stripObjectToScalars(user), + mod: userToConfigAccessibleUser(mod), + user: userToConfigAccessibleUser(user), caseNumber: createdCase.case_number, reason: caseArgs.reason, }); diff --git a/backend/src/plugins/Mutes/types.ts b/backend/src/plugins/Mutes/types.ts index cb0a6820..c0afcc18 100644 --- a/backend/src/plugins/Mutes/types.ts +++ b/backend/src/plugins/Mutes/types.ts @@ -1,16 +1,17 @@ -import * as t from "io-ts"; -import { tNullable, UserNotificationMethod, UserNotificationResult } from "../../utils"; -import { Mute } from "../../data/entities/Mute"; -import { Member } from "eris"; -import { Case } from "../../data/entities/Case"; -import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub"; -import { GuildLogs } from "../../data/GuildLogs"; -import { GuildCases } from "../../data/GuildCases"; -import { GuildArchives } from "../../data/GuildArchives"; -import { GuildMutes } from "../../data/GuildMutes"; -import { CaseArgs } from "../Cases/types"; -import Timeout = NodeJS.Timeout; +import { GuildMember } from "discord.js"; import { EventEmitter } from "events"; +import * as t from "io-ts"; +import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub"; +import { Case } from "../../data/entities/Case"; +import { Mute } from "../../data/entities/Mute"; +import { GuildArchives } from "../../data/GuildArchives"; +import { GuildCases } from "../../data/GuildCases"; +import { GuildLogs } from "../../data/GuildLogs"; +import { GuildMutes } from "../../data/GuildMutes"; +import { tNullable, UserNotificationMethod, UserNotificationResult } from "../../utils"; +import { CaseArgs } from "../Cases/types"; + +import Timeout = NodeJS.Timeout; export const ConfigSchema = t.type({ mute_role: tNullable(t.string), @@ -58,7 +59,7 @@ export interface MutesPluginType extends BasePluginType { } export interface IMuteWithDetails extends Mute { - member?: Member; + member?: GuildMember; banned?: boolean; } diff --git a/backend/src/plugins/NameHistory/NameHistoryPlugin.ts b/backend/src/plugins/NameHistory/NameHistoryPlugin.ts index af95ea7a..250cace5 100644 --- a/backend/src/plugins/NameHistory/NameHistoryPlugin.ts +++ b/backend/src/plugins/NameHistory/NameHistoryPlugin.ts @@ -1,11 +1,11 @@ import { PluginOptions } from "knub"; -import { ConfigSchema, NameHistoryPluginType } from "./types"; -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { GuildNicknameHistory } from "../../data/GuildNicknameHistory"; import { UsernameHistory } from "../../data/UsernameHistory"; import { Queue } from "../../Queue"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { NamesCmd } from "./commands/NamesCmd"; import { ChannelJoinEvt, MessageCreateEvt } from "./events/UpdateNameEvts"; +import { ConfigSchema, NameHistoryPluginType } from "./types"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/NameHistory/commands/NamesCmd.ts b/backend/src/plugins/NameHistory/commands/NamesCmd.ts index 78456820..710e7ba6 100644 --- a/backend/src/plugins/NameHistory/commands/NamesCmd.ts +++ b/backend/src/plugins/NameHistory/commands/NamesCmd.ts @@ -1,11 +1,12 @@ -import { nameHistoryCmd } from "../types"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { Snowflake } from "discord.js"; import { createChunkedMessage, disableCodeBlocks } from "knub/dist/helpers"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; import { NICKNAME_RETENTION_PERIOD } from "../../../data/cleanup/nicknames"; -import { DAYS } from "../../../utils"; import { MAX_NICKNAME_ENTRIES_PER_USER } from "../../../data/GuildNicknameHistory"; import { MAX_USERNAME_ENTRIES_PER_USER } from "../../../data/UsernameHistory"; import { sendErrorMessage } from "../../../pluginUtils"; +import { DAYS } from "../../../utils"; +import { nameHistoryCmd } from "../types"; export const NamesCmd = nameHistoryCmd({ trigger: "names", @@ -29,8 +30,8 @@ export const NamesCmd = nameHistoryCmd({ ); const usernameRows = usernames.map(r => `\`[${r.timestamp}]\` **${disableCodeBlocks(r.username)}**`); - const user = pluginData.client.users.get(args.userId); - const currentUsername = user ? `${user.username}#${user.discriminator}` : args.userId; + const user = await pluginData.client.users.fetch(args.userId as Snowflake).catch(() => null); + const currentUsername = user ? user.tag : args.userId; const nicknameDays = Math.round(NICKNAME_RETENTION_PERIOD / DAYS); const usernameDays = Math.round(NICKNAME_RETENTION_PERIOD / DAYS); diff --git a/backend/src/plugins/NameHistory/events/UpdateNameEvts.ts b/backend/src/plugins/NameHistory/events/UpdateNameEvts.ts index dc3d451b..0d644663 100644 --- a/backend/src/plugins/NameHistory/events/UpdateNameEvts.ts +++ b/backend/src/plugins/NameHistory/events/UpdateNameEvts.ts @@ -2,10 +2,12 @@ import { nameHistoryEvt } from "../types"; import { updateNickname } from "../updateNickname"; export const ChannelJoinEvt = nameHistoryEvt({ - event: "voiceChannelJoin", + event: "voiceStateUpdate", async listener(meta) { - meta.pluginData.state.updateQueue.add(() => updateNickname(meta.pluginData, meta.args.member)); + meta.pluginData.state.updateQueue.add(() => + updateNickname(meta.pluginData, meta.args.newState.member ?? meta.args.oldState.member!), + ); }, }); diff --git a/backend/src/plugins/NameHistory/updateNickname.ts b/backend/src/plugins/NameHistory/updateNickname.ts index 2ac8471f..e843ae3b 100644 --- a/backend/src/plugins/NameHistory/updateNickname.ts +++ b/backend/src/plugins/NameHistory/updateNickname.ts @@ -1,12 +1,12 @@ -import { Member } from "eris"; +import { GuildMember } from "discord.js"; import { GuildPluginData } from "knub"; import { NameHistoryPluginType } from "./types"; -export async function updateNickname(pluginData: GuildPluginData, member: Member) { +export async function updateNickname(pluginData: GuildPluginData, member: GuildMember) { if (!member) return; const latestEntry = await pluginData.state.nicknameHistory.getLastEntry(member.id); - if (!latestEntry || latestEntry.nickname !== member.nick) { - if (!latestEntry && member.nick == null) return; // No need to save "no nickname" if there's no previous data - await pluginData.state.nicknameHistory.addEntry(member.id, member.nick); + if (!latestEntry || latestEntry.nickname !== member.nickname) { + if (!latestEntry && member.nickname == null) return; // No need to save "no nickname" if there's no previous data + await pluginData.state.nicknameHistory.addEntry(member.id, member.nickname); } } diff --git a/backend/src/plugins/Persist/PersistPlugin.ts b/backend/src/plugins/Persist/PersistPlugin.ts index 25b74d11..ede40235 100644 --- a/backend/src/plugins/Persist/PersistPlugin.ts +++ b/backend/src/plugins/Persist/PersistPlugin.ts @@ -1,12 +1,12 @@ import { PluginOptions } from "knub"; -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { ConfigSchema, PersistPluginType } from "./types"; -import { GuildPersistedData } from "../../data/GuildPersistedData"; import { GuildLogs } from "../../data/GuildLogs"; -import { StoreDataEvt } from "./events/StoreDataEvt"; -import { LoadDataEvt } from "./events/LoadDataEvt"; +import { GuildPersistedData } from "../../data/GuildPersistedData"; import { trimPluginDescription } from "../../utils"; import { LogsPlugin } from "../Logs/LogsPlugin"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import { LoadDataEvt } from "./events/LoadDataEvt"; +import { StoreDataEvt } from "./events/StoreDataEvt"; +import { ConfigSchema, PersistPluginType } from "./types"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/Persist/events/LoadDataEvt.ts b/backend/src/plugins/Persist/events/LoadDataEvt.ts index cef12dbf..75c3871d 100644 --- a/backend/src/plugins/Persist/events/LoadDataEvt.ts +++ b/backend/src/plugins/Persist/events/LoadDataEvt.ts @@ -1,15 +1,15 @@ -import { persistEvt } from "../types"; -import { Constants, MemberOptions } from "eris"; +import { GuildMemberEditData, Permissions } from "discord.js"; import intersection from "lodash.intersection"; +import { memberToConfigAccessibleMember } from "../../../utils/configAccessibleObjects"; import { LogType } from "../../../data/LogType"; -import { stripObjectToScalars } from "../../../utils"; -import { getMissingPermissions } from "../../../utils/getMissingPermissions"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { missingPermissionError } from "../../../utils/missingPermissionError"; import { canAssignRole } from "../../../utils/canAssignRole"; +import { getMissingPermissions } from "../../../utils/getMissingPermissions"; import { memberRolesLock } from "../../../utils/lockNameHelpers"; +import { missingPermissionError } from "../../../utils/missingPermissionError"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { persistEvt } from "../types"; -const p = Constants.Permissions; +const p = Permissions.FLAGS; export const LoadDataEvt = persistEvt({ event: "guildMemberAdd", @@ -26,16 +26,16 @@ export const LoadDataEvt = persistEvt({ return; } - const toRestore: MemberOptions = {}; + const toRestore: GuildMemberEditData = {}; const config = await pluginData.config.getForMember(member); const restoredData: string[] = []; // Check permissions - const me = pluginData.guild.members.get(pluginData.client.user.id)!; + const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!; let requiredPermissions = 0n; - if (config.persist_nicknames) requiredPermissions |= p.manageNicknames; - if (config.persisted_roles) requiredPermissions |= p.manageRoles; - const missingPermissions = getMissingPermissions(me.permission, requiredPermissions); + if (config.persist_nicknames) requiredPermissions |= p.MANAGE_NICKNAMES; + if (config.persisted_roles) requiredPermissions |= p.MANAGE_ROLES; + const missingPermissions = getMissingPermissions(me.permissions, requiredPermissions); if (missingPermissions) { pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, { body: `Missing permissions for persist plugin: ${missingPermissionError(missingPermissions)}`, @@ -61,7 +61,7 @@ export const LoadDataEvt = persistEvt({ if (rolesToRestore.length) { restoredData.push("roles"); - toRestore.roles = Array.from(new Set([...rolesToRestore, ...member.roles])); + toRestore.roles = Array.from(new Set([...rolesToRestore, ...member.roles.cache])); } } @@ -75,7 +75,7 @@ export const LoadDataEvt = persistEvt({ await pluginData.state.persistedData.clear(member.id); pluginData.state.logs.log(LogType.MEMBER_RESTORE, { - member: stripObjectToScalars(member, ["user", "roles"]), + member: memberToConfigAccessibleMember(member), restoredData: restoredData.join(", "), }); } diff --git a/backend/src/plugins/Persist/events/StoreDataEvt.ts b/backend/src/plugins/Persist/events/StoreDataEvt.ts index 66d2c79e..aad0c48e 100644 --- a/backend/src/plugins/Persist/events/StoreDataEvt.ts +++ b/backend/src/plugins/Persist/events/StoreDataEvt.ts @@ -1,13 +1,13 @@ -import { persistEvt } from "../types"; -import { IPartialPersistData } from "../../../data/GuildPersistedData"; -import { Member } from "eris"; +import { GuildMember } from "discord.js"; import intersection from "lodash.intersection"; +import { IPartialPersistData } from "../../../data/GuildPersistedData"; +import { persistEvt } from "../types"; export const StoreDataEvt = persistEvt({ event: "guildMemberRemove", async listener(meta) { - const member = meta.args.member as Member; + const member = meta.args.member as GuildMember; const pluginData = meta.pluginData; let persist = false; @@ -23,9 +23,9 @@ export const StoreDataEvt = persistEvt({ } } - if (config.persist_nicknames && member.nick) { + if (config.persist_nicknames && member.nickname) { persist = true; - persistData.nickname = member.nick; + persistData.nickname = member.nickname; } if (persist) { diff --git a/backend/src/plugins/Persist/types.ts b/backend/src/plugins/Persist/types.ts index 2c360daf..8475f4be 100644 --- a/backend/src/plugins/Persist/types.ts +++ b/backend/src/plugins/Persist/types.ts @@ -1,7 +1,7 @@ import * as t from "io-ts"; import { BasePluginType, typedGuildEventListener } from "knub"; -import { GuildPersistedData } from "../../data/GuildPersistedData"; import { GuildLogs } from "../../data/GuildLogs"; +import { GuildPersistedData } from "../../data/GuildPersistedData"; export const ConfigSchema = t.type({ persisted_roles: t.array(t.string), diff --git a/backend/src/plugins/PingableRoles/PingableRolesPlugin.ts b/backend/src/plugins/PingableRoles/PingableRolesPlugin.ts index de1fa15c..8b4f8e61 100644 --- a/backend/src/plugins/PingableRoles/PingableRolesPlugin.ts +++ b/backend/src/plugins/PingableRoles/PingableRolesPlugin.ts @@ -1,10 +1,10 @@ import { PluginOptions } from "knub"; -import { ConfigSchema, PingableRolesPluginType } from "./types"; -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { GuildPingableRoles } from "../../data/GuildPingableRoles"; -import { PingableRoleEnableCmd } from "./commands/PingableRoleEnableCmd"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { PingableRoleDisableCmd } from "./commands/PingableRoleDisableCmd"; +import { PingableRoleEnableCmd } from "./commands/PingableRoleEnableCmd"; import { MessageCreateDisablePingableEvt, TypingEnablePingableEvt } from "./events/ChangePingableEvts"; +import { ConfigSchema, PingableRolesPluginType } from "./types"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/PingableRoles/commands/PingableRoleDisableCmd.ts b/backend/src/plugins/PingableRoles/commands/PingableRoleDisableCmd.ts index 02dc86cf..41709ffc 100644 --- a/backend/src/plugins/PingableRoles/commands/PingableRoleDisableCmd.ts +++ b/backend/src/plugins/PingableRoles/commands/PingableRoleDisableCmd.ts @@ -1,6 +1,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { pingableRolesCmd } from "../types"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { pingableRolesCmd } from "../types"; export const PingableRoleDisableCmd = pingableRolesCmd({ trigger: ["pingable_role disable", "pingable_role d"], diff --git a/backend/src/plugins/PingableRoles/commands/PingableRoleEnableCmd.ts b/backend/src/plugins/PingableRoles/commands/PingableRoleEnableCmd.ts index 6cf6ef8d..30077e09 100644 --- a/backend/src/plugins/PingableRoles/commands/PingableRoleEnableCmd.ts +++ b/backend/src/plugins/PingableRoles/commands/PingableRoleEnableCmd.ts @@ -1,6 +1,6 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { pingableRolesCmd } from "../types"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { pingableRolesCmd } from "../types"; export const PingableRoleEnableCmd = pingableRolesCmd({ trigger: "pingable_role", diff --git a/backend/src/plugins/PingableRoles/events/ChangePingableEvts.ts b/backend/src/plugins/PingableRoles/events/ChangePingableEvts.ts index 82afa7f2..8153f094 100644 --- a/backend/src/plugins/PingableRoles/events/ChangePingableEvts.ts +++ b/backend/src/plugins/PingableRoles/events/ChangePingableEvts.ts @@ -1,7 +1,7 @@ import { pingableRolesEvt } from "../types"; -import { getPingableRolesForChannel } from "../utils/getPingableRolesForChannel"; -import { enablePingableRoles } from "../utils/enablePingableRoles"; import { disablePingableRoles } from "../utils/disablePingableRoles"; +import { enablePingableRoles } from "../utils/enablePingableRoles"; +import { getPingableRolesForChannel } from "../utils/getPingableRolesForChannel"; const TIMEOUT = 10 * 1000; diff --git a/backend/src/plugins/PingableRoles/types.ts b/backend/src/plugins/PingableRoles/types.ts index 84030afd..cc479a99 100644 --- a/backend/src/plugins/PingableRoles/types.ts +++ b/backend/src/plugins/PingableRoles/types.ts @@ -1,7 +1,7 @@ import * as t from "io-ts"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub"; -import { GuildPingableRoles } from "../../data/GuildPingableRoles"; import { PingableRole } from "../../data/entities/PingableRole"; +import { GuildPingableRoles } from "../../data/GuildPingableRoles"; export const ConfigSchema = t.type({ can_manage: t.boolean, diff --git a/backend/src/plugins/PingableRoles/utils/disablePingableRoles.ts b/backend/src/plugins/PingableRoles/utils/disablePingableRoles.ts index f4d735b1..8730b588 100644 --- a/backend/src/plugins/PingableRoles/utils/disablePingableRoles.ts +++ b/backend/src/plugins/PingableRoles/utils/disablePingableRoles.ts @@ -1,5 +1,6 @@ -import { PingableRole } from "../../../data/entities/PingableRole"; +import { Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; +import { PingableRole } from "../../../data/entities/PingableRole"; import { PingableRolesPluginType } from "../types"; export function disablePingableRoles( @@ -7,7 +8,7 @@ export function disablePingableRoles( pingableRoles: PingableRole[], ) { for (const pingableRole of pingableRoles) { - const role = pluginData.guild.roles.get(pingableRole.role_id); + const role = pluginData.guild.roles.cache.get(pingableRole.role_id as Snowflake); if (!role) continue; role.edit( diff --git a/backend/src/plugins/PingableRoles/utils/enablePingableRoles.ts b/backend/src/plugins/PingableRoles/utils/enablePingableRoles.ts index f4ab93c9..a7c40e78 100644 --- a/backend/src/plugins/PingableRoles/utils/enablePingableRoles.ts +++ b/backend/src/plugins/PingableRoles/utils/enablePingableRoles.ts @@ -1,5 +1,6 @@ -import { PingableRole } from "../../../data/entities/PingableRole"; +import { Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; +import { PingableRole } from "../../../data/entities/PingableRole"; import { PingableRolesPluginType } from "../types"; export function enablePingableRoles( @@ -7,7 +8,7 @@ export function enablePingableRoles( pingableRoles: PingableRole[], ) { for (const pingableRole of pingableRoles) { - const role = pluginData.guild.roles.get(pingableRole.role_id); + const role = pluginData.guild.roles.cache.get(pingableRole.role_id as Snowflake); if (!role) continue; role.edit( diff --git a/backend/src/plugins/PingableRoles/utils/getPingableRolesForChannel.ts b/backend/src/plugins/PingableRoles/utils/getPingableRolesForChannel.ts index 9b3f5819..96b6b228 100644 --- a/backend/src/plugins/PingableRoles/utils/getPingableRolesForChannel.ts +++ b/backend/src/plugins/PingableRoles/utils/getPingableRolesForChannel.ts @@ -1,5 +1,5 @@ -import { PingableRole } from "../../../data/entities/PingableRole"; import { GuildPluginData } from "knub"; +import { PingableRole } from "../../../data/entities/PingableRole"; import { PingableRolesPluginType } from "../types"; export async function getPingableRolesForChannel( diff --git a/backend/src/plugins/Post/PostPlugin.ts b/backend/src/plugins/Post/PostPlugin.ts index 85ec4e12..493f8adb 100644 --- a/backend/src/plugins/Post/PostPlugin.ts +++ b/backend/src/plugins/Post/PostPlugin.ts @@ -1,18 +1,18 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { PluginOptions } from "knub"; -import { ConfigSchema, PostPluginType } from "./types"; +import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildScheduledPosts } from "../../data/GuildScheduledPosts"; -import { GuildLogs } from "../../data/GuildLogs"; -import { PostCmd } from "./commands/PostCmd"; -import { PostEmbedCmd } from "./commands/PostEmbedCmd"; +import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { EditCmd } from "./commands/EditCmd"; import { EditEmbedCmd } from "./commands/EditEmbedCmd"; -import { ScheduledPostsShowCmd } from "./commands/ScheduledPostsShowCmd"; -import { ScheduledPostsListCmd } from "./commands/ScheduledPostsListCmd"; +import { PostCmd } from "./commands/PostCmd"; +import { PostEmbedCmd } from "./commands/PostEmbedCmd"; import { ScheduledPostsDeleteCmd } from "./commands/SchedluedPostsDeleteCmd"; +import { ScheduledPostsListCmd } from "./commands/ScheduledPostsListCmd"; +import { ScheduledPostsShowCmd } from "./commands/ScheduledPostsShowCmd"; +import { ConfigSchema, PostPluginType } from "./types"; import { scheduledPostLoop } from "./util/scheduledPostLoop"; -import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/Post/commands/EditCmd.ts b/backend/src/plugins/Post/commands/EditCmd.ts index 64f471e2..1835abca 100644 --- a/backend/src/plugins/Post/commands/EditCmd.ts +++ b/backend/src/plugins/Post/commands/EditCmd.ts @@ -1,6 +1,7 @@ -import { postCmd } from "../types"; +import { Snowflake, TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { postCmd } from "../types"; import { formatContent } from "../util/formatContent"; export const EditCmd = postCmd({ @@ -19,12 +20,17 @@ export const EditCmd = postCmd({ return; } - if (savedMessage.user_id !== pluginData.client.user.id) { + if (savedMessage.user_id !== pluginData.client.user!.id) { sendErrorMessage(pluginData, msg.channel, "Message wasn't posted by me"); return; } - await pluginData.client.editMessage(savedMessage.channel_id, savedMessage.id, formatContent(args.content)); + (pluginData.guild.channels.cache.get(savedMessage.channel_id as Snowflake) as TextChannel).messages.edit( + savedMessage.id as Snowflake, + { + content: formatContent(args.content), + }, + ); sendSuccessMessage(pluginData, msg.channel, "Message edited"); }, }); diff --git a/backend/src/plugins/Post/commands/EditEmbedCmd.ts b/backend/src/plugins/Post/commands/EditEmbedCmd.ts index edd1a49c..a8155d98 100644 --- a/backend/src/plugins/Post/commands/EditEmbedCmd.ts +++ b/backend/src/plugins/Post/commands/EditEmbedCmd.ts @@ -1,11 +1,11 @@ -import { postCmd } from "../types"; +import { MessageEmbed, Snowflake, TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { Embed } from "eris"; import { trimLines } from "../../../utils"; -import { formatContent } from "../util/formatContent"; import { parseColor } from "../../../utils/parseColor"; import { rgbToInt } from "../../../utils/rgbToInt"; +import { postCmd } from "../types"; +import { formatContent } from "../util/formatContent"; const COLOR_MATCH_REGEX = /^#?([0-9a-f]{6})$/; @@ -42,17 +42,22 @@ export const EditEmbedCmd = postCmd({ } } - const embed: Embed = savedMessage.data.embeds![0] as Embed; + const embed: MessageEmbed = savedMessage.data.embeds![0] as MessageEmbed; if (args.title) embed.title = args.title; if (content) embed.description = formatContent(content); if (color) embed.color = color; - await pluginData.client.editMessage(savedMessage.channel_id, savedMessage.id, { embed }); + (pluginData.guild.channels.cache.get(savedMessage.channel_id as Snowflake) as TextChannel).messages.edit( + savedMessage.id as Snowflake, + { + embeds: [embed], + }, + ); await sendSuccessMessage(pluginData, msg.channel, "Embed edited"); if (args.content) { const prefix = pluginData.fullConfig.prefix || "!"; - msg.channel.createMessage( + msg.channel.send( trimLines(` <@!${msg.author.id}> You can now specify an embed's content directly at the end of the command: \`${prefix}edit_embed -title "Some title" content goes here\` diff --git a/backend/src/plugins/Post/commands/PostCmd.ts b/backend/src/plugins/Post/commands/PostCmd.ts index 25ae5a6b..4a1f3a4a 100644 --- a/backend/src/plugins/Post/commands/PostCmd.ts +++ b/backend/src/plugins/Post/commands/PostCmd.ts @@ -1,5 +1,5 @@ -import { postCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { postCmd } from "../types"; import { actualPostCmd } from "../util/actualPostCmd"; export const PostCmd = postCmd({ diff --git a/backend/src/plugins/Post/commands/PostEmbedCmd.ts b/backend/src/plugins/Post/commands/PostEmbedCmd.ts index 324effdd..6db7d1cc 100644 --- a/backend/src/plugins/Post/commands/PostEmbedCmd.ts +++ b/backend/src/plugins/Post/commands/PostEmbedCmd.ts @@ -1,12 +1,12 @@ -import { postCmd } from "../types"; +import { MessageEmbedOptions } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { actualPostCmd } from "../util/actualPostCmd"; import { sendErrorMessage } from "../../../pluginUtils"; -import { Embed } from "eris"; import { isValidEmbed, trimLines } from "../../../utils"; -import { formatContent } from "../util/formatContent"; import { parseColor } from "../../../utils/parseColor"; import { rgbToInt } from "../../../utils/rgbToInt"; +import { postCmd } from "../types"; +import { actualPostCmd } from "../util/actualPostCmd"; +import { formatContent } from "../util/formatContent"; export const PostEmbedCmd = postCmd({ trigger: "post_embed", @@ -46,7 +46,7 @@ export const PostEmbedCmd = postCmd({ } } - let embed: Embed = { type: "rich" }; + let embed: MessageEmbedOptions = {}; if (args.title) embed.title = args.title; if (color) embed.color = color; @@ -73,7 +73,7 @@ export const PostEmbedCmd = postCmd({ if (args.content) { const prefix = pluginData.fullConfig.prefix || "!"; - msg.channel.createMessage( + msg.channel.send( trimLines(` <@!${msg.author.id}> You can now specify an embed's content directly at the end of the command: \`${prefix}edit_embed -title "Some title" content goes here\` diff --git a/backend/src/plugins/Post/commands/SchedluedPostsDeleteCmd.ts b/backend/src/plugins/Post/commands/SchedluedPostsDeleteCmd.ts index 98810fc1..0cda8a72 100644 --- a/backend/src/plugins/Post/commands/SchedluedPostsDeleteCmd.ts +++ b/backend/src/plugins/Post/commands/SchedluedPostsDeleteCmd.ts @@ -1,7 +1,7 @@ -import { postCmd } from "../types"; -import { sorter } from "../../../utils"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { sorter } from "../../../utils"; +import { postCmd } from "../types"; export const ScheduledPostsDeleteCmd = postCmd({ trigger: ["scheduled_posts delete", "scheduled_posts d"], diff --git a/backend/src/plugins/Post/commands/ScheduledPostsListCmd.ts b/backend/src/plugins/Post/commands/ScheduledPostsListCmd.ts index bc61a4ba..7ac59698 100644 --- a/backend/src/plugins/Post/commands/ScheduledPostsListCmd.ts +++ b/backend/src/plugins/Post/commands/ScheduledPostsListCmd.ts @@ -1,15 +1,9 @@ -import { postCmd } from "../types"; -import { - trimLines, - sorter, - disableCodeBlocks, - deactivateMentions, - createChunkedMessage, - DBDateFormat, -} from "../../../utils"; +import { Util } from "discord.js"; import humanizeDuration from "humanize-duration"; import moment from "moment-timezone"; +import { createChunkedMessage, DBDateFormat, deactivateMentions, sorter, trimLines } from "../../../utils"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { postCmd } from "../types"; const SCHEDULED_POST_PREVIEW_TEXT_LENGTH = 50; @@ -20,7 +14,7 @@ export const ScheduledPostsListCmd = postCmd({ async run({ message: msg, pluginData }) { const scheduledPosts = await pluginData.state.scheduledPosts.all(); if (scheduledPosts.length === 0) { - msg.channel.createMessage("No scheduled posts"); + msg.channel.send("No scheduled posts"); return; } @@ -33,7 +27,7 @@ export const ScheduledPostsListCmd = postCmd({ const isTruncated = previewText.length > SCHEDULED_POST_PREVIEW_TEXT_LENGTH; - previewText = disableCodeBlocks(deactivateMentions(previewText)) + previewText = Util.escapeCodeBlock(deactivateMentions(previewText)) .replace(/\s+/g, " ") .slice(0, SCHEDULED_POST_PREVIEW_TEXT_LENGTH); @@ -61,7 +55,7 @@ export const ScheduledPostsListCmd = postCmd({ const finalMessage = trimLines(` ${postLines.join("\n")} - + Use \`scheduled_posts \` to view a scheduled post in full Use \`scheduled_posts delete \` to delete a scheduled post `); diff --git a/backend/src/plugins/Post/commands/ScheduledPostsShowCmd.ts b/backend/src/plugins/Post/commands/ScheduledPostsShowCmd.ts index 25ecdede..c1a92510 100644 --- a/backend/src/plugins/Post/commands/ScheduledPostsShowCmd.ts +++ b/backend/src/plugins/Post/commands/ScheduledPostsShowCmd.ts @@ -1,9 +1,9 @@ -import { postCmd } from "../types"; -import { sorter } from "../../../utils"; -import { sendErrorMessage } from "../../../pluginUtils"; +import { TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { sendErrorMessage } from "../../../pluginUtils"; +import { sorter } from "../../../utils"; +import { postCmd } from "../types"; import { postMessage } from "../util/postMessage"; -import { TextChannel } from "eris"; export const ScheduledPostsShowCmd = postCmd({ trigger: ["scheduled_posts", "scheduled_posts show"], diff --git a/backend/src/plugins/Post/types.ts b/backend/src/plugins/Post/types.ts index 12800a55..b54a23d2 100644 --- a/backend/src/plugins/Post/types.ts +++ b/backend/src/plugins/Post/types.ts @@ -1,8 +1,8 @@ import * as t from "io-ts"; import { BasePluginType, typedGuildCommand } from "knub"; +import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildScheduledPosts } from "../../data/GuildScheduledPosts"; -import { GuildLogs } from "../../data/GuildLogs"; export const ConfigSchema = t.type({ can_post: t.boolean, diff --git a/backend/src/plugins/Post/util/actualPostCmd.ts b/backend/src/plugins/Post/util/actualPostCmd.ts index b8c8cbc8..d6865759 100644 --- a/backend/src/plugins/Post/util/actualPostCmd.ts +++ b/backend/src/plugins/Post/util/actualPostCmd.ts @@ -1,14 +1,15 @@ -import { Message, Channel, TextChannel } from "eris"; -import { StrictMessageContent, errorMessage, stripObjectToScalars, MINUTES, DBDateFormat } from "../../../utils"; -import moment from "moment-timezone"; -import { LogType } from "../../../data/LogType"; +import { Channel, Message, TextChannel } from "discord.js"; import humanizeDuration from "humanize-duration"; -import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { GuildPluginData } from "knub"; +import moment from "moment-timezone"; +import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { LogType } from "../../../data/LogType"; +import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { DBDateFormat, errorMessage, MINUTES, StrictMessageContent } from "../../../utils"; +import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; import { PostPluginType } from "../types"; import { parseScheduleTime } from "./parseScheduleTime"; import { postMessage } from "./postMessage"; -import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; const MIN_REPEAT_TIME = 5 * MINUTES; const MAX_REPEAT_TIME = Math.pow(2, 32); @@ -28,22 +29,30 @@ export async function actualPostCmd( } = {}, ) { if (!(targetChannel instanceof TextChannel)) { - msg.channel.createMessage(errorMessage("Channel is not a text channel")); + msg.channel.send(errorMessage("Channel is not a text channel")); return; } - if (content == null && msg.attachments.length === 0) { - msg.channel.createMessage(errorMessage("Message content or attachment required")); + if (content == null && msg.attachments.size === 0) { + msg.channel.send(errorMessage("Message content or attachment required")); return; } if (opts.repeat) { if (opts.repeat < MIN_REPEAT_TIME) { - sendErrorMessage(pluginData, msg.channel, `Minimum time for -repeat is ${humanizeDuration(MIN_REPEAT_TIME)}`); + sendErrorMessage( + pluginData, + msg.channel as TextChannel, + `Minimum time for -repeat is ${humanizeDuration(MIN_REPEAT_TIME)}`, + ); return; } if (opts.repeat > MAX_REPEAT_TIME) { - sendErrorMessage(pluginData, msg.channel, `Max time for -repeat is ${humanizeDuration(MAX_REPEAT_TIME)}`); + sendErrorMessage( + pluginData, + msg.channel as TextChannel, + `Max time for -repeat is ${humanizeDuration(MAX_REPEAT_TIME)}`, + ); return; } } @@ -54,7 +63,7 @@ export async function actualPostCmd( // Schedule the post to be posted later postAt = await parseScheduleTime(pluginData, msg.author.id, opts.schedule); if (!postAt) { - sendErrorMessage(pluginData, msg.channel, "Invalid schedule time"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "Invalid schedule time"); return; } } else if (opts.repeat) { @@ -71,17 +80,17 @@ export async function actualPostCmd( // Invalid time if (!repeatUntil) { - sendErrorMessage(pluginData, msg.channel, "Invalid time specified for -repeat-until"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "Invalid time specified for -repeat-until"); return; } if (repeatUntil.isBefore(moment.utc())) { - sendErrorMessage(pluginData, msg.channel, "You can't set -repeat-until in the past"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "You can't set -repeat-until in the past"); return; } if (repeatUntil.isAfter(MAX_REPEAT_UNTIL)) { sendErrorMessage( pluginData, - msg.channel, + msg.channel as TextChannel, "Unfortunately, -repeat-until can only be at most 100 years into the future. Maybe 99 years would be enough?", ); return; @@ -89,18 +98,26 @@ export async function actualPostCmd( } else if (opts["repeat-times"]) { repeatTimes = opts["repeat-times"]; if (repeatTimes <= 0) { - sendErrorMessage(pluginData, msg.channel, "-repeat-times must be 1 or more"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "-repeat-times must be 1 or more"); return; } } if (repeatUntil && repeatTimes) { - sendErrorMessage(pluginData, msg.channel, "You can only use one of -repeat-until or -repeat-times at once"); + sendErrorMessage( + pluginData, + msg.channel as TextChannel, + "You can only use one of -repeat-until or -repeat-times at once", + ); return; } if (opts.repeat && !repeatUntil && !repeatTimes) { - sendErrorMessage(pluginData, msg.channel, "You must specify -repeat-until or -repeat-times for repeated messages"); + sendErrorMessage( + pluginData, + msg.channel as TextChannel, + "You must specify -repeat-until or -repeat-times for repeated messages", + ); return; } @@ -115,16 +132,16 @@ export async function actualPostCmd( // Save schedule/repeat information in DB if (postAt) { if (postAt < moment.utc()) { - sendErrorMessage(pluginData, msg.channel, "Post can't be scheduled to be posted in the past"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "Post can't be scheduled to be posted in the past"); return; } await pluginData.state.scheduledPosts.create({ author_id: msg.author.id, - author_name: `${msg.author.username}#${msg.author.discriminator}`, + author_name: msg.author.tag, channel_id: targetChannel.id, content, - attachments: msg.attachments, + attachments: [...msg.attachments.values()], post_at: postAt .clone() .tz("Etc/UTC") @@ -142,8 +159,8 @@ export async function actualPostCmd( if (opts.repeat) { pluginData.state.logs.log(LogType.SCHEDULED_REPEATED_MESSAGE, { - author: stripObjectToScalars(msg.author), - channel: stripObjectToScalars(targetChannel), + author: userToConfigAccessibleUser(msg.author), + channel: channelToConfigAccessibleChannel(targetChannel), datetime: postAt.format(timeAndDate.getDateFormat("pretty_datetime")), date: postAt.format(timeAndDate.getDateFormat("date")), time: postAt.format(timeAndDate.getDateFormat("time")), @@ -152,8 +169,8 @@ export async function actualPostCmd( }); } else { pluginData.state.logs.log(LogType.SCHEDULED_MESSAGE, { - author: stripObjectToScalars(msg.author), - channel: stripObjectToScalars(targetChannel), + author: userToConfigAccessibleUser(msg.author), + channel: channelToConfigAccessibleChannel(targetChannel), datetime: postAt.format(timeAndDate.getDateFormat("pretty_datetime")), date: postAt.format(timeAndDate.getDateFormat("date")), time: postAt.format(timeAndDate.getDateFormat("time")), @@ -163,13 +180,13 @@ export async function actualPostCmd( // When the message isn't scheduled for later, post it immediately if (!opts.schedule) { - await postMessage(pluginData, targetChannel, content, msg.attachments, opts["enable-mentions"]); + await postMessage(pluginData, targetChannel, content, [...msg.attachments.values()], opts["enable-mentions"]); } if (opts.repeat) { pluginData.state.logs.log(LogType.REPEATED_MESSAGE, { - author: stripObjectToScalars(msg.author), - channel: stripObjectToScalars(targetChannel), + author: userToConfigAccessibleUser(msg.author), + channel: channelToConfigAccessibleChannel(targetChannel), datetime: postAt.format(timeAndDate.getDateFormat("pretty_datetime")), date: postAt.format(timeAndDate.getDateFormat("date")), time: postAt.format(timeAndDate.getDateFormat("time")), @@ -198,6 +215,6 @@ export async function actualPostCmd( } if (targetChannel.id !== msg.channel.id || opts.schedule || opts.repeat) { - sendSuccessMessage(pluginData, msg.channel, successMessage); + sendSuccessMessage(pluginData, msg.channel as TextChannel, successMessage); } } diff --git a/backend/src/plugins/Post/util/parseScheduleTime.ts b/backend/src/plugins/Post/util/parseScheduleTime.ts index 26d115f2..5f5325e5 100644 --- a/backend/src/plugins/Post/util/parseScheduleTime.ts +++ b/backend/src/plugins/Post/util/parseScheduleTime.ts @@ -1,6 +1,6 @@ +import { GuildPluginData } from "knub"; import moment, { Moment } from "moment-timezone"; import { convertDelayStringToMS } from "../../../utils"; -import { GuildPluginData } from "knub"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; // TODO: Extract out of the Post plugin, use everywhere with a date input diff --git a/backend/src/plugins/Post/util/postMessage.ts b/backend/src/plugins/Post/util/postMessage.ts index 97726869..9d4b67f4 100644 --- a/backend/src/plugins/Post/util/postMessage.ts +++ b/backend/src/plugins/Post/util/postMessage.ts @@ -1,8 +1,8 @@ -import { GuildPluginData } from "knub"; -import { PostPluginType } from "../types"; -import { Attachment, Message, MessageContent, TextChannel } from "eris"; -import { downloadFile } from "../../../utils"; +import { Message, MessageAttachment, MessageOptions, TextChannel } from "discord.js"; import fs from "fs"; +import { GuildPluginData } from "knub"; +import { downloadFile } from "../../../utils"; +import { PostPluginType } from "../types"; import { formatContent } from "./formatContent"; const fsp = fs.promises; @@ -10,8 +10,8 @@ const fsp = fs.promises; export async function postMessage( pluginData: GuildPluginData, channel: TextChannel, - content: MessageContent, - attachments: Attachment[] = [], + content: MessageOptions, + attachments: MessageAttachment[] = [], enableMentions: boolean = false, ): Promise { if (typeof content === "string") { @@ -27,20 +27,18 @@ export async function postMessage( if (attachments.length) { downloadedAttachment = await downloadFile(attachments[0].url); file = { - name: attachments[0].filename, + name: attachments[0].name, file: await fsp.readFile(downloadedAttachment.path), }; } if (enableMentions) { content.allowedMentions = { - everyone: true, - users: true, - roles: true, + parse: ["everyone", "roles", "users"], }; } - const createdMsg = await channel.createMessage(content, file); + const createdMsg = await channel.send({ ...content, files: [file] }); pluginData.state.savedMessages.setPermanent(createdMsg.id); if (downloadedAttachment) { diff --git a/backend/src/plugins/Post/util/scheduledPostLoop.ts b/backend/src/plugins/Post/util/scheduledPostLoop.ts index 08ebd516..766daed6 100644 --- a/backend/src/plugins/Post/util/scheduledPostLoop.ts +++ b/backend/src/plugins/Post/util/scheduledPostLoop.ts @@ -1,10 +1,11 @@ +import { Snowflake, TextChannel, User } from "discord.js"; import { GuildPluginData } from "knub"; -import { PostPluginType } from "../types"; -import { logger } from "../../../logger"; -import { stripObjectToScalars, SECONDS, DBDateFormat } from "../../../utils"; -import { LogType } from "../../../data/LogType"; import moment from "moment-timezone"; -import { TextChannel, User } from "eris"; +import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { LogType } from "../../../data/LogType"; +import { logger } from "../../../logger"; +import { DBDateFormat, SECONDS } from "../../../utils"; +import { PostPluginType } from "../types"; import { postMessage } from "./postMessage"; const SCHEDULED_POST_CHECK_INTERVAL = 5 * SECONDS; @@ -12,10 +13,10 @@ const SCHEDULED_POST_CHECK_INTERVAL = 5 * SECONDS; export async function scheduledPostLoop(pluginData: GuildPluginData) { const duePosts = await pluginData.state.scheduledPosts.getDueScheduledPosts(); for (const post of duePosts) { - const channel = pluginData.guild.channels.get(post.channel_id); + const channel = pluginData.guild.channels.cache.get(post.channel_id as Snowflake); if (channel instanceof TextChannel) { const [username, discriminator] = post.author_name.split("#"); - const author: Partial = pluginData.client.users.get(post.author_id) || { + const author: User = (await pluginData.client.users.fetch(post.author_id as Snowflake)) || { id: post.author_id, username, discriminator, @@ -30,15 +31,15 @@ export async function scheduledPostLoop(pluginData: GuildPluginData = { config: { + button_groups: {}, auto_refresh_interval: MIN_AUTO_REFRESH, remove_user_reactions: true, @@ -31,6 +40,67 @@ const defaultOptions: PluginOptions = { ], }; +const MAXIMUM_COMPONENT_ROWS = 5; + +const configPreprocessor: ConfigPreprocessorFn = options => { + if (options.config.button_groups) { + for (const [groupName, group] of Object.entries(options.config.button_groups)) { + const defaultButtonNames = Object.keys(group.default_buttons); + const defaultButtons = Object.values(group.default_buttons); + const menuNames = Object.keys(group.button_menus ?? []); + + const defaultBtnRowCount = getRowCount(defaultButtons); + if (defaultBtnRowCount > MAXIMUM_COMPONENT_ROWS || defaultBtnRowCount === 0) { + throw new StrictValidationError([ + `Invalid row count for default_buttons: You currently have ${defaultBtnRowCount}, the maximum is 5. A new row is started automatically each 5 consecutive buttons.`, + ]); + } + + for (let i = 0; i < defaultButtons.length; i++) { + const defBtn = defaultButtons[i]; + if (!menuNames.includes(defBtn.role_or_menu) && !isValidSnowflake(defBtn.role_or_menu)) { + throw new StrictValidationError([ + `Invalid value for default_buttons/${defaultButtonNames[i]}/role_or_menu: ${defBtn.role_or_menu} is neither an existing menu nor a valid snowflake.`, + ]); + } + if (!defBtn.label && !defBtn.emoji) { + throw new StrictValidationError([ + `Invalid values for default_buttons/${defaultButtonNames[i]}/(label|emoji): Must have label, emoji or both set for the button to be valid.`, + ]); + } + } + + for (const [menuName, menuButtonEntries] of Object.entries(group.button_menus ?? [])) { + const menuButtonNames = Object.keys(menuButtonEntries); + const menuButtons = Object.values(menuButtonEntries); + + const menuButtonRowCount = getRowCount(menuButtons); + if (menuButtonRowCount > MAXIMUM_COMPONENT_ROWS || menuButtonRowCount === 0) { + throw new StrictValidationError([ + `Invalid row count for button_menus/${menuName}: You currently have ${menuButtonRowCount}, the maximum is 5. A new row is started automatically each 5 consecutive buttons.`, + ]); + } + + for (let i = 0; i < menuButtons.length; i++) { + const menuBtn = menuButtons[i]; + if (!menuNames.includes(menuBtn.role_or_menu) && !isValidSnowflake(menuBtn.role_or_menu)) { + throw new StrictValidationError([ + `Invalid value for button_menus/${menuButtonNames[i]}/role_or_menu: ${menuBtn.role_or_menu} is neither an existing menu nor a valid snowflake.`, + ]); + } + if (!menuBtn.label && !menuBtn.emoji) { + throw new StrictValidationError([ + `Invalid values for default_buttons/${defaultButtonNames[i]}/(label|emoji): Must have label, emoji or both set for the button to be valid.`, + ]); + } + } + } + } + } + + return options; +}; + export const ReactionRolesPlugin = zeppelinGuildPlugin()({ name: "reaction_roles", showInDocs: true, @@ -47,18 +117,23 @@ export const ReactionRolesPlugin = zeppelinGuildPlugin( RefreshReactionRolesCmd, ClearReactionRolesCmd, InitReactionRolesCmd, + PostButtonRolesCmd, ], // prettier-ignore events: [ AddReactionRoleEvt, + ButtonInteractionEvt, + MessageDeletedEvt, ], + configPreprocessor, beforeLoad(pluginData) { const { state, guild } = pluginData; state.reactionRoles = GuildReactionRoles.getGuildInstance(guild.id); state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id); + state.buttonRoles = GuildButtonRoles.getGuildInstance(guild.id); state.reactionRemoveQueue = new Queue(); state.roleChangeQueue = new Queue(); state.pendingRoleChanges = new Map(); diff --git a/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts index b5f78fe7..229b8fbe 100644 --- a/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/ClearReactionRolesCmd.ts @@ -1,8 +1,8 @@ -import { reactionRolesCmd } from "../types"; +import { Message, Snowflake } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { Message, TextChannel } from "eris"; -import { isDiscordRESTError } from "../../../utils"; +import { isDiscordAPIError } from "../../../utils"; +import { reactionRolesCmd } from "../types"; export const ClearReactionRolesCmd = reactionRolesCmd({ trigger: "reaction_roles clear", @@ -21,11 +21,11 @@ export const ClearReactionRolesCmd = reactionRolesCmd({ pluginData.state.reactionRoles.removeFromMessage(args.message.messageId); - let targetMessage: Message; + let targetMessage: Message; try { - targetMessage = await args.message.channel.getMessage(args.message.messageId); + targetMessage = await args.message.channel.messages.fetch(args.message.messageId as Snowflake); } catch (err) { - if (isDiscordRESTError(err) && err.code === 50001) { + if (isDiscordAPIError(err) && err.code === 50001) { sendErrorMessage(pluginData, msg.channel, "Missing access to the specified message"); return; } @@ -33,7 +33,7 @@ export const ClearReactionRolesCmd = reactionRolesCmd({ throw err; } - await targetMessage.removeReactions(); + await targetMessage.reactions.removeAll(); sendSuccessMessage(pluginData, msg.channel, "Reaction roles cleared"); }, diff --git a/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts index 119b7b27..dba3db59 100644 --- a/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts @@ -1,11 +1,10 @@ -import { reactionRolesCmd, TReactionRolePair } from "../types"; +import { Snowflake } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { TextChannel } from "eris"; -import { RecoverablePluginError, ERRORS } from "../../../RecoverablePluginError"; -import { canUseEmoji, isDiscordRESTError, isValidEmoji, noop, trimPluginDescription } from "../../../utils"; -import { applyReactionRoleReactionsToMessage } from "../util/applyReactionRoleReactionsToMessage"; +import { canUseEmoji, isDiscordAPIError, isValidEmoji, noop, trimPluginDescription } from "../../../utils"; import { canReadChannel } from "../../../utils/canReadChannel"; +import { reactionRolesCmd, TReactionRolePair } from "../types"; +import { applyReactionRoleReactionsToMessage } from "../util/applyReactionRoleReactionsToMessage"; const CLEAR_ROLES_EMOJI = "❌"; @@ -41,9 +40,9 @@ export const InitReactionRolesCmd = reactionRolesCmd({ let targetMessage; try { - targetMessage = await args.message.channel.getMessage(args.message.messageId).catch(noop); + targetMessage = await args.message.channel.messages.fetch(args.message.messageId as Snowflake).catch(noop); } catch (e) { - if (isDiscordRESTError(e)) { + if (isDiscordAPIError(e)) { sendErrorMessage(pluginData, msg.channel, `Error ${e.code} while getting message: ${e.message}`); return; } @@ -96,15 +95,16 @@ export const InitReactionRolesCmd = reactionRolesCmd({ return; } - if (!pluginData.guild.roles.has(pair[1])) { + if (!pluginData.guild.roles.cache.has(pair[1] as Snowflake)) { sendErrorMessage(pluginData, msg.channel, `Unknown role ${pair[1]}`); return; } } - const progressMessage = msg.channel.createMessage("Adding reaction roles..."); + const progressMessage = msg.channel.send("Adding reaction roles..."); // Save the new reaction roles to the database + let pos = 0; for (const pair of emojiRolePairs) { await pluginData.state.reactionRoles.add( args.message.channel.id, @@ -112,7 +112,9 @@ export const InitReactionRolesCmd = reactionRolesCmd({ pair[0], pair[1], args.exclusive, + pos, ); + pos++; } // Apply the reactions themselves diff --git a/backend/src/plugins/ReactionRoles/commands/PostButtonRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/PostButtonRolesCmd.ts new file mode 100644 index 00000000..040acf1b --- /dev/null +++ b/backend/src/plugins/ReactionRoles/commands/PostButtonRolesCmd.ts @@ -0,0 +1,73 @@ +import { createHash } from "crypto"; +import { MessageButton, Snowflake } from "discord.js"; +import moment from "moment"; +import { sendErrorMessage, sendSuccessMessage } from "src/pluginUtils"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { reactionRolesCmd } from "../types"; +import { splitButtonsIntoRows } from "../util/splitButtonsIntoRows"; + +export const PostButtonRolesCmd = reactionRolesCmd({ + trigger: "reaction_roles post", + permission: "can_manage", + + signature: { + channel: ct.textChannel(), + buttonGroup: ct.string(), + }, + + async run({ message: msg, args, pluginData }) { + const cfg = pluginData.config.get(); + if (!cfg.button_groups) { + sendErrorMessage(pluginData, msg.channel, "No button groups defined in config"); + return; + } + const group = cfg.button_groups[args.buttonGroup]; + + if (!group) { + sendErrorMessage(pluginData, msg.channel, `No button group matches the name **${args.buttonGroup}**`); + return; + } + + const buttons: MessageButton[] = []; + const toInsert: Array<{ customId; buttonGroup; buttonName }> = []; + for (const [buttonName, button] of Object.entries(group.default_buttons)) { + const customId = createHash("md5") + .update(`${buttonName}${moment.utc().valueOf()}`) + .digest("hex"); + + const btn = new MessageButton() + .setLabel(button.label ?? "") + .setStyle(button.style ?? "PRIMARY") + .setCustomId(customId) + .setDisabled(button.disabled ?? false); + + if (button.emoji) { + const emo = pluginData.client.emojis.resolve(button.emoji as Snowflake) ?? button.emoji; + btn.setEmoji(emo); + } + + buttons.push(btn); + toInsert.push({ customId, buttonGroup: args.buttonGroup, buttonName }); + } + const rows = splitButtonsIntoRows(buttons, Object.values(group.default_buttons)); // new MessageActionRow().addComponents(buttons); + + try { + const newMsg = await args.channel.send({ content: group.message, components: rows }); + + for (const btn of toInsert) { + await pluginData.state.buttonRoles.add( + args.channel.id, + newMsg.id, + btn.customId, + btn.buttonGroup, + btn.buttonName, + ); + } + } catch (e) { + sendErrorMessage(pluginData, msg.channel, `Error trying to post message: ${e}`); + return; + } + + await sendSuccessMessage(pluginData, msg.channel, `Successfully posted message in <#${args.channel.id}>`); + }, +}); diff --git a/backend/src/plugins/ReactionRoles/commands/RefreshReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/RefreshReactionRolesCmd.ts index 7a93165b..3a55f6ba 100644 --- a/backend/src/plugins/ReactionRoles/commands/RefreshReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/RefreshReactionRolesCmd.ts @@ -1,6 +1,6 @@ -import { reactionRolesCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { reactionRolesCmd } from "../types"; import { refreshReactionRoles } from "../util/refreshReactionRoles"; export const RefreshReactionRolesCmd = reactionRolesCmd({ diff --git a/backend/src/plugins/ReactionRoles/events/AddReactionRoleEvt.ts b/backend/src/plugins/ReactionRoles/events/AddReactionRoleEvt.ts index b93c9456..1d7a116f 100644 --- a/backend/src/plugins/ReactionRoles/events/AddReactionRoleEvt.ts +++ b/backend/src/plugins/ReactionRoles/events/AddReactionRoleEvt.ts @@ -1,9 +1,7 @@ -import { reactionRolesEvt } from "../types"; +import { Message } from "discord.js"; import { noop, resolveMember, sleep } from "../../../utils"; +import { reactionRolesEvt } from "../types"; import { addMemberPendingRoleChange } from "../util/addMemberPendingRoleChange"; -import { DiscordRESTError, Message } from "eris"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { LogType } from "../../../data/LogType"; const CLEAR_ROLES_EMOJI = "❌"; @@ -12,11 +10,11 @@ export const AddReactionRoleEvt = reactionRolesEvt({ async listener(meta) { const pluginData = meta.pluginData; - const msg = meta.args.message as Message; - const emoji = meta.args.emoji; - const userId = meta.args.member.id; + const msg = meta.args.reaction.message as Message; + const emoji = meta.args.reaction.emoji; + const userId = meta.args.user.id; - if (userId === pluginData.client.user.id) { + if (userId === pluginData.client.user!.id) { // Don't act on own reactions // FIXME: This may not be needed? Knub currently requires the *member* to be found for the user to be resolved as well. Need to look into it more. return; @@ -39,7 +37,7 @@ export const AddReactionRoleEvt = reactionRolesEvt({ // User reacted with a reaction role emoji -> add the role const matchingReactionRole = await pluginData.state.reactionRoles.getByMessageAndEmoji( msg.id, - emoji.id || emoji.name, + emoji.id || emoji.name!, ); if (!matchingReactionRole) return; @@ -59,9 +57,8 @@ export const AddReactionRoleEvt = reactionRolesEvt({ if (config.remove_user_reactions) { setTimeout(() => { pluginData.state.reactionRemoveQueue.add(async () => { - const reaction = emoji.id ? `${emoji.name}:${emoji.id}` : emoji.name; const wait = sleep(1500); - await msg.channel.removeMessageReaction(msg.id, reaction, userId).catch(noop); + await meta.args.reaction.users.remove(userId).catch(noop); await wait; }); }, 1500); diff --git a/backend/src/plugins/ReactionRoles/events/ButtonInteractionEvt.ts b/backend/src/plugins/ReactionRoles/events/ButtonInteractionEvt.ts new file mode 100644 index 00000000..3d7fb3d5 --- /dev/null +++ b/backend/src/plugins/ReactionRoles/events/ButtonInteractionEvt.ts @@ -0,0 +1,92 @@ +import { MessageComponentInteraction } from "discord.js"; +import humanizeDuration from "humanize-duration"; +import moment from "moment"; +import { LogType } from "src/data/LogType"; +import { logger } from "src/logger"; +import { LogsPlugin } from "src/plugins/Logs/LogsPlugin"; +import { MINUTES } from "src/utils"; +import { idToTimestamp } from "src/utils/idToTimestamp"; +import { reactionRolesEvt } from "../types"; +import { handleModifyRole, handleOpenMenu } from "../util/buttonActionHandlers"; +import { BUTTON_CONTEXT_SEPARATOR, resolveStatefulCustomId } from "../util/buttonCustomIdFunctions"; +import { ButtonMenuActions } from "../util/buttonMenuActions"; + +const BUTTON_INVALIDATION_TIME = 15 * MINUTES; + +export const ButtonInteractionEvt = reactionRolesEvt({ + event: "interactionCreate", + + async listener(meta) { + const int = meta.args.interaction; + if (!int.isMessageComponent()) return; + + const cfg = meta.pluginData.config.get(); + const split = int.customId.split(BUTTON_CONTEXT_SEPARATOR); + const context = (await resolveStatefulCustomId(meta.pluginData, int.customId)) ?? { + groupName: split[0], + action: split[1], + roleOrMenu: split[2], + stateless: true, + }; + + if (context.stateless) { + if (context.roleOrMenu == null) { + // Not reaction from this plugin + return; + } + const timeSinceCreation = moment.utc().valueOf() - idToTimestamp(int.message.id)!; + if (timeSinceCreation >= BUTTON_INVALIDATION_TIME) { + sendEphemeralReply( + int, + `Sorry, but these buttons are invalid because they are older than ${humanizeDuration( + BUTTON_INVALIDATION_TIME, + )}.\nIf the menu is still available, open it again to assign yourself roles!`, + ); + return; + } + } + + const group = cfg.button_groups[context.groupName]; + if (!group) { + await sendEphemeralReply(int, `A configuration error was encountered, please contact the Administrators!`); + meta.pluginData + .getPlugin(LogsPlugin) + .log( + LogType.BOT_ALERT, + `**A configuration error occurred** on buttons for message ${int.message.id}, group **${context.groupName}** not found in config`, + ); + return; + } + + // Verify that detected action is known by us + if (!(Object).values(ButtonMenuActions).includes(context.action)) { + await sendEphemeralReply(int, `A internal error was encountered, please contact the Administrators!`); + meta.pluginData + .getPlugin(LogsPlugin) + .log( + LogType.BOT_ALERT, + `**A internal error occurred** on buttons for message ${int.message.id}, action **${context.action}** is not known`, + ); + return; + } + + if (context.action === ButtonMenuActions.MODIFY_ROLE) { + await handleModifyRole(meta.pluginData, int, group, context); + return; + } + + if (context.action === ButtonMenuActions.OPEN_MENU) { + await handleOpenMenu(meta.pluginData, int, group, context); + return; + } + + logger.warn( + `Action ${context.action} on button ${int.customId} (Guild: ${int.guildId}, Channel: ${int.channelId}) is unknown!`, + ); + await sendEphemeralReply(int, `A internal error was encountered, please contact the Administrators!`); + }, +}); + +async function sendEphemeralReply(interaction: MessageComponentInteraction, message: string) { + await interaction.reply({ content: message, ephemeral: true }); +} diff --git a/backend/src/plugins/ReactionRoles/events/MessageDeletedEvt.ts b/backend/src/plugins/ReactionRoles/events/MessageDeletedEvt.ts new file mode 100644 index 00000000..323da037 --- /dev/null +++ b/backend/src/plugins/ReactionRoles/events/MessageDeletedEvt.ts @@ -0,0 +1,14 @@ +import { reactionRolesEvt } from "../types"; + +export const MessageDeletedEvt = reactionRolesEvt({ + event: "messageDelete", + allowBots: true, + allowSelf: true, + + async listener(meta) { + const pluginData = meta.pluginData; + + await pluginData.state.buttonRoles.removeAllForMessageId(meta.args.message.id); + await pluginData.state.reactionRoles.removeFromMessage(meta.args.message.id); + }, +}); diff --git a/backend/src/plugins/ReactionRoles/types.ts b/backend/src/plugins/ReactionRoles/types.ts index 36fed600..27dc5d17 100644 --- a/backend/src/plugins/ReactionRoles/types.ts +++ b/backend/src/plugins/ReactionRoles/types.ts @@ -1,10 +1,40 @@ import * as t from "io-ts"; -import { BasePluginType, typedGuildEventListener, typedGuildCommand } from "knub"; -import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub"; +import { GuildButtonRoles } from "src/data/GuildButtonRoles"; import { GuildReactionRoles } from "../../data/GuildReactionRoles"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { Queue } from "../../Queue"; +import { tNullable } from "../../utils"; + +// These need to be updated every time discord adds/removes a style, +// but i cant figure out how to import MessageButtonStyles at runtime +enum ButtonStyles { + PRIMARY = 1, + SECONDARY = 2, + SUCCESS = 3, + DANGER = 4, + // LINK = 5, We do not want users to create link buttons, but it would be style 5 +} + +const ButtonOpts = t.type({ + label: tNullable(t.string), + emoji: tNullable(t.string), + role_or_menu: t.string, + style: tNullable(t.keyof(ButtonStyles)), // https://discord.js.org/#/docs/main/master/typedef/MessageButtonStyle + disabled: tNullable(t.boolean), + end_row: tNullable(t.boolean), +}); +export type TButtonOpts = t.TypeOf; + +const ButtonPairOpts = t.type({ + message: t.string, + default_buttons: t.record(t.string, ButtonOpts), + button_menus: tNullable(t.record(t.string, t.record(t.string, ButtonOpts))), +}); +export type TButtonPairOpts = t.TypeOf; export const ConfigSchema = t.type({ + button_groups: t.record(t.string, ButtonPairOpts), auto_refresh_interval: t.number, remove_user_reactions: t.boolean, can_manage: t.boolean, @@ -31,6 +61,7 @@ export interface ReactionRolesPluginType extends BasePluginType { state: { reactionRoles: GuildReactionRoles; savedMessages: GuildSavedMessages; + buttonRoles: GuildButtonRoles; reactionRemoveQueue: Queue; roleChangeQueue: Queue; diff --git a/backend/src/plugins/ReactionRoles/util/addMemberPendingRoleChange.ts b/backend/src/plugins/ReactionRoles/util/addMemberPendingRoleChange.ts index 00ae6108..78c681c2 100644 --- a/backend/src/plugins/ReactionRoles/util/addMemberPendingRoleChange.ts +++ b/backend/src/plugins/ReactionRoles/util/addMemberPendingRoleChange.ts @@ -1,8 +1,9 @@ +import { Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; -import { ReactionRolesPluginType, RoleChangeMode, PendingMemberRoleChanges } from "../types"; -import { resolveMember } from "../../../utils"; import { logger } from "../../../logger"; +import { resolveMember } from "../../../utils"; import { memberRolesLock } from "../../../utils/lockNameHelpers"; +import { PendingMemberRoleChanges, ReactionRolesPluginType, RoleChangeMode } from "../types"; const ROLE_CHANGE_BATCH_DEBOUNCE_TIME = 1500; @@ -23,10 +24,10 @@ export async function addMemberPendingRoleChange( const member = await resolveMember(pluginData.client, pluginData.guild, memberId); if (member) { - const newRoleIds = new Set(member.roles); + const newRoleIds = new Set(member.roles.cache.keys()); for (const change of newPendingRoleChangeObj.changes) { - if (change.mode === "+") newRoleIds.add(change.roleId); - else newRoleIds.delete(change.roleId); + if (change.mode === "+") newRoleIds.add(change.roleId as Snowflake); + else newRoleIds.delete(change.roleId as Snowflake); } try { @@ -37,9 +38,7 @@ export async function addMemberPendingRoleChange( "Reaction roles", ); } catch (e) { - logger.warn( - `Failed to apply role changes to ${member.username}#${member.discriminator} (${member.id}): ${e.message}`, - ); + logger.warn(`Failed to apply role changes to ${member.user.tag} (${member.id}): ${e.message}`); } } lock.unlock(); diff --git a/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts b/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts index 7de45269..8dafc9fe 100644 --- a/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts +++ b/backend/src/plugins/ReactionRoles/util/applyReactionRoleReactionsToMessage.ts @@ -1,11 +1,10 @@ +import { Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { ReactionRolesPluginType } from "../types"; import { ReactionRole } from "../../../data/entities/ReactionRole"; -import { TextChannel } from "eris"; -import { isDiscordRESTError, sleep, isSnowflake } from "../../../utils"; -import { logger } from "../../../logger"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; import { LogType } from "../../../data/LogType"; +import { isDiscordAPIError, sleep } from "../../../utils"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { ReactionRolesPluginType } from "../types"; const CLEAR_ROLES_EMOJI = "❌"; @@ -18,7 +17,7 @@ export async function applyReactionRoleReactionsToMessage( messageId: string, reactionRoles: ReactionRole[], ): Promise { - const channel = pluginData.guild.channels.get(channelId) as TextChannel; + const channel = pluginData.guild.channels.cache.get(channelId as Snowflake) as TextChannel; if (!channel) return; const errors: string[] = []; @@ -26,9 +25,9 @@ export async function applyReactionRoleReactionsToMessage( let targetMessage; try { - targetMessage = await channel.getMessage(messageId); + targetMessage = await channel.messages.fetch(messageId as Snowflake); } catch (e) { - if (isDiscordRESTError(e)) { + if (isDiscordAPIError(e)) { if (e.code === 10008) { // Unknown message, remove reaction roles from the message logs.log(LogType.BOT_ALERT, { @@ -50,9 +49,9 @@ export async function applyReactionRoleReactionsToMessage( // Remove old reactions, if any try { - await targetMessage.removeReactions(); + await targetMessage.reactions.removeAll(); } catch (e) { - if (isDiscordRESTError(e)) { + if (isDiscordAPIError(e)) { errors.push(`Error ${e.code} while removing old reactions: ${e.message}`); logs.log(LogType.BOT_ALERT, { body: `Error ${e.code} while removing old reaction role reactions from message ${channelId}/${messageId}: ${e.message}`, @@ -70,18 +69,16 @@ export async function applyReactionRoleReactionsToMessage( emojisToAdd.push(CLEAR_ROLES_EMOJI); for (const rawEmoji of emojisToAdd) { - const emoji = isSnowflake(rawEmoji) ? `foo:${rawEmoji}` : rawEmoji; - try { - await targetMessage.addReaction(emoji); - await sleep(1250); // Make sure we don't hit rate limits + await targetMessage.react(rawEmoji); + await sleep(750); // Make sure we don't hit rate limits } catch (e) { - if (isDiscordRESTError(e)) { + if (isDiscordAPIError(e)) { if (e.code === 10014) { pluginData.state.reactionRoles.removeFromMessage(messageId, rawEmoji); - errors.push(`Unknown emoji: ${emoji}`); + errors.push(`Unknown emoji: ${rawEmoji}`); logs.log(LogType.BOT_ALERT, { - body: `Could not add unknown reaction role emoji ${emoji} to message ${channelId}/${messageId}`, + body: `Could not add unknown reaction role emoji ${rawEmoji} to message ${channelId}/${messageId}`, }); continue; } else if (e.code === 50013) { diff --git a/backend/src/plugins/ReactionRoles/util/buttonActionHandlers.ts b/backend/src/plugins/ReactionRoles/util/buttonActionHandlers.ts new file mode 100644 index 00000000..446cc0ee --- /dev/null +++ b/backend/src/plugins/ReactionRoles/util/buttonActionHandlers.ts @@ -0,0 +1,106 @@ +import { MessageButton, MessageComponentInteraction, Snowflake } from "discord.js"; +import { GuildPluginData } from "knub"; +import { LogType } from "../../../data/LogType"; +import { LogsPlugin } from "../../../plugins/Logs/LogsPlugin"; +import { ReactionRolesPluginType, TButtonPairOpts } from "../types"; +import { generateStatelessCustomId } from "./buttonCustomIdFunctions"; +import { splitButtonsIntoRows } from "./splitButtonsIntoRows"; + +export async function handleOpenMenu( + pluginData: GuildPluginData, + int: MessageComponentInteraction, + group: TButtonPairOpts, + context, +) { + const menuButtons: MessageButton[] = []; + if (group.button_menus == null) { + await int.reply({ + content: `A configuration error was encountered, please contact the Administrators!`, + ephemeral: true, + }); + pluginData + .getPlugin(LogsPlugin) + .log( + LogType.BOT_ALERT, + `**A configuration error occurred** on buttons for message ${int.message.id}, no menus found in config`, + ); + return; + } + + for (const menuButton of Object.values(group.button_menus[context.roleOrMenu])) { + const customId = await generateStatelessCustomId(pluginData, context.groupName, menuButton.role_or_menu); + + const btn = new MessageButton() + .setLabel(menuButton.label ?? "") + .setStyle("PRIMARY") + .setCustomId(customId) + .setDisabled(menuButton.disabled ?? false); + + if (menuButton.emoji) { + const emo = pluginData.client.emojis.resolve(menuButton.emoji as Snowflake) ?? menuButton.emoji; + btn.setEmoji(emo); + } + menuButtons.push(btn); + } + + if (menuButtons.length === 0) { + await int.reply({ + content: `A configuration error was encountered, please contact the Administrators!`, + ephemeral: true, + }); + pluginData + .getPlugin(LogsPlugin) + .log( + LogType.BOT_ALERT, + `**A configuration error occurred** on buttons for message ${int.message.id}, menu **${context.roleOrMenu}** not found in config`, + ); + return; + } + const rows = splitButtonsIntoRows(menuButtons, Object.values(group.button_menus[context.roleOrMenu])); // new MessageActionRow().addComponents(menuButtons); + + int.reply({ content: `Click to add/remove a role`, components: rows, ephemeral: true }); +} + +export async function handleModifyRole( + pluginData: GuildPluginData, + int: MessageComponentInteraction, + group: TButtonPairOpts, + context, +) { + const role = await pluginData.guild.roles.fetch(context.roleOrMenu); + if (!role) { + await int.reply({ + content: `A configuration error was encountered, please contact the Administrators!`, + ephemeral: true, + }); + pluginData + .getPlugin(LogsPlugin) + .log( + LogType.BOT_ALERT, + `**A configuration error occurred** on buttons for message ${int.message.id}, role **${context.roleOrMenu}** not found on server`, + ); + return; + } + + const member = await pluginData.guild.members.fetch(int.user.id); + try { + if (member.roles.cache.has(role.id)) { + await member.roles.remove(role, `Button Roles on message ${int.message.id}`); + await int.reply({ content: `Role **${role.name}** removed`, ephemeral: true }); + } else { + await member.roles.add(role, `Button Roles on message ${int.message.id}`); + await int.reply({ content: `Role **${role.name}** added`, ephemeral: true }); + } + } catch (e) { + await int.reply({ + content: "A configuration error was encountered, please contact the Administrators!", + ephemeral: true, + }); + pluginData + .getPlugin(LogsPlugin) + .log( + LogType.BOT_ALERT, + `**A configuration error occurred** on buttons for message ${int.message.id}, error: ${e}. We might be missing permissions!`, + ); + } +} diff --git a/backend/src/plugins/ReactionRoles/util/buttonCustomIdFunctions.ts b/backend/src/plugins/ReactionRoles/util/buttonCustomIdFunctions.ts new file mode 100644 index 00000000..fa3644da --- /dev/null +++ b/backend/src/plugins/ReactionRoles/util/buttonCustomIdFunctions.ts @@ -0,0 +1,45 @@ +import { Snowflake } from "discord.js"; +import { GuildPluginData } from "knub"; +import { ReactionRolesPluginType } from "../types"; +import { ButtonMenuActions } from "./buttonMenuActions"; + +export const BUTTON_CONTEXT_SEPARATOR = ":rb:"; + +export async function getButtonAction(pluginData: GuildPluginData, roleOrMenu: string) { + if (await pluginData.guild.roles.fetch(roleOrMenu as Snowflake).catch(() => false)) { + return ButtonMenuActions.MODIFY_ROLE; + } else { + return ButtonMenuActions.OPEN_MENU; + } +} + +export async function generateStatelessCustomId( + pluginData: GuildPluginData, + groupName: string, + roleOrMenu: string, +) { + let id = groupName + BUTTON_CONTEXT_SEPARATOR; + + id += `${await getButtonAction(pluginData, roleOrMenu)}${BUTTON_CONTEXT_SEPARATOR}${roleOrMenu}`; + + return id; +} + +export async function resolveStatefulCustomId(pluginData: GuildPluginData, id: string) { + const button = await pluginData.state.buttonRoles.getForButtonId(id); + + if (button) { + const group = pluginData.config.get().button_groups[button.button_group]; + if (!group) return null; + const cfgButton = group.default_buttons[button.button_name]; + + return { + groupName: button.button_group, + action: await getButtonAction(pluginData, cfgButton.role_or_menu), + roleOrMenu: cfgButton.role_or_menu, + stateless: false, + }; + } else { + return null; + } +} diff --git a/backend/src/plugins/ReactionRoles/util/buttonMenuActions.ts b/backend/src/plugins/ReactionRoles/util/buttonMenuActions.ts new file mode 100644 index 00000000..89c77ff7 --- /dev/null +++ b/backend/src/plugins/ReactionRoles/util/buttonMenuActions.ts @@ -0,0 +1,4 @@ +export enum ButtonMenuActions { + OPEN_MENU = "goto", + MODIFY_ROLE = "grant", +} diff --git a/backend/src/plugins/ReactionRoles/util/refreshReactionRoles.ts b/backend/src/plugins/ReactionRoles/util/refreshReactionRoles.ts index 3d85ac52..1cb3ff80 100644 --- a/backend/src/plugins/ReactionRoles/util/refreshReactionRoles.ts +++ b/backend/src/plugins/ReactionRoles/util/refreshReactionRoles.ts @@ -1,5 +1,5 @@ -import { ReactionRolesPluginType } from "../types"; import { GuildPluginData } from "knub"; +import { ReactionRolesPluginType } from "../types"; import { applyReactionRoleReactionsToMessage } from "./applyReactionRoleReactionsToMessage"; export async function refreshReactionRoles( diff --git a/backend/src/plugins/ReactionRoles/util/splitButtonsIntoRows.ts b/backend/src/plugins/ReactionRoles/util/splitButtonsIntoRows.ts new file mode 100644 index 00000000..8d73ab0a --- /dev/null +++ b/backend/src/plugins/ReactionRoles/util/splitButtonsIntoRows.ts @@ -0,0 +1,42 @@ +import { MessageActionRow, MessageButton } from "discord.js"; +import { TButtonOpts } from "../types"; + +export function splitButtonsIntoRows(actualButtons: MessageButton[], configButtons: TButtonOpts[]): MessageActionRow[] { + const rows: MessageActionRow[] = []; + let curRow = new MessageActionRow(); + let consecutive = 0; + + for (let i = 0; i < actualButtons.length; i++) { + const aBtn = actualButtons[i]; + const cBtn = configButtons[i]; + + curRow.addComponents(aBtn); + if (((consecutive + 1) % 5 === 0 || cBtn.end_row) && i + 1 < actualButtons.length) { + rows.push(curRow); + curRow = new MessageActionRow(); + consecutive = 0; + } else { + consecutive++; + } + } + + if (curRow.components.length >= 1) rows.push(curRow); + return rows; +} + +export function getRowCount(configButtons: TButtonOpts[]): number { + let count = 1; + let consecutive = 0; + for (let i = 0; i < configButtons.length; i++) { + const cBtn = configButtons[i]; + + if (((consecutive + 1) % 5 === 0 || cBtn.end_row) && i + 1 < configButtons.length) { + count++; + consecutive = 0; + } else { + consecutive++; + } + } + + return count; +} diff --git a/backend/src/plugins/Reminders/RemindersPlugin.ts b/backend/src/plugins/Reminders/RemindersPlugin.ts index 928a013f..e1a39417 100644 --- a/backend/src/plugins/Reminders/RemindersPlugin.ts +++ b/backend/src/plugins/Reminders/RemindersPlugin.ts @@ -1,12 +1,12 @@ import { PluginOptions } from "knub"; -import { ConfigSchema, RemindersPluginType } from "./types"; -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { GuildReminders } from "../../data/GuildReminders"; -import { postDueRemindersLoop } from "./utils/postDueRemindersLoop"; +import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { RemindCmd } from "./commands/RemindCmd"; import { RemindersCmd } from "./commands/RemindersCmd"; import { RemindersDeleteCmd } from "./commands/RemindersDeleteCmd"; -import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; +import { ConfigSchema, RemindersPluginType } from "./types"; +import { postDueRemindersLoop } from "./utils/postDueRemindersLoop"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/Reminders/commands/RemindCmd.ts b/backend/src/plugins/Reminders/commands/RemindCmd.ts index ad9cd355..7cce547f 100644 --- a/backend/src/plugins/Reminders/commands/RemindCmd.ts +++ b/backend/src/plugins/Reminders/commands/RemindCmd.ts @@ -1,10 +1,10 @@ -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import moment from "moment-timezone"; -import { convertDelayStringToMS, messageLink } from "../../../utils"; import humanizeDuration from "humanize-duration"; +import moment from "moment-timezone"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { remindersCmd } from "../types"; +import { convertDelayStringToMS, messageLink } from "../../../utils"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { remindersCmd } from "../types"; export const RemindCmd = remindersCmd({ trigger: ["remind", "remindme", "reminder"], diff --git a/backend/src/plugins/Reminders/commands/RemindersCmd.ts b/backend/src/plugins/Reminders/commands/RemindersCmd.ts index 1a32533e..ca61ceb8 100644 --- a/backend/src/plugins/Reminders/commands/RemindersCmd.ts +++ b/backend/src/plugins/Reminders/commands/RemindersCmd.ts @@ -1,9 +1,9 @@ -import { remindersCmd } from "../types"; +import humanizeDuration from "humanize-duration"; +import moment from "moment-timezone"; import { sendErrorMessage } from "../../../pluginUtils"; import { createChunkedMessage, DBDateFormat, sorter } from "../../../utils"; -import moment from "moment-timezone"; -import humanizeDuration from "humanize-duration"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { remindersCmd } from "../types"; export const RemindersCmd = remindersCmd({ trigger: "reminders", diff --git a/backend/src/plugins/Reminders/commands/RemindersDeleteCmd.ts b/backend/src/plugins/Reminders/commands/RemindersDeleteCmd.ts index 7b9f33c6..e9f97a66 100644 --- a/backend/src/plugins/Reminders/commands/RemindersDeleteCmd.ts +++ b/backend/src/plugins/Reminders/commands/RemindersDeleteCmd.ts @@ -1,7 +1,7 @@ -import { remindersCmd } from "../types"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { sorter } from "../../../utils"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { remindersCmd } from "../types"; export const RemindersDeleteCmd = remindersCmd({ trigger: ["reminders delete", "reminders d"], diff --git a/backend/src/plugins/Reminders/utils/postDueRemindersLoop.ts b/backend/src/plugins/Reminders/utils/postDueRemindersLoop.ts index 36c2fb5e..b5fc6257 100644 --- a/backend/src/plugins/Reminders/utils/postDueRemindersLoop.ts +++ b/backend/src/plugins/Reminders/utils/postDueRemindersLoop.ts @@ -1,10 +1,10 @@ -import { TextChannel } from "eris"; -import { GuildPluginData } from "knub"; -import { RemindersPluginType } from "../types"; -import moment from "moment-timezone"; +import { Snowflake, TextChannel } from "discord.js"; import humanizeDuration from "humanize-duration"; +import { GuildPluginData } from "knub"; import { disableLinkPreviews } from "knub/dist/helpers"; +import moment from "moment-timezone"; import { SECONDS } from "../../../utils"; +import { RemindersPluginType } from "../types"; const REMINDER_LOOP_TIME = 10 * SECONDS; const MAX_TRIES = 3; @@ -12,7 +12,7 @@ const MAX_TRIES = 3; export async function postDueRemindersLoop(pluginData: GuildPluginData) { const pendingReminders = await pluginData.state.reminders.getDueReminders(); for (const reminder of pendingReminders) { - const channel = pluginData.guild.channels.get(reminder.channel_id); + const channel = pluginData.guild.channels.cache.get(reminder.channel_id as Snowflake); if (channel && channel instanceof TextChannel) { try { // Only show created at date if one exists @@ -20,19 +20,19 @@ export async function postDueRemindersLoop(pluginData: GuildPluginData: ${reminder.body} \n\`Set at ${reminder.created_at} (${result} ago)\``, ), allowedMentions: { - users: [reminder.user_id], + users: [reminder.user_id as Snowflake], }, }); } else { - await channel.createMessage({ + await channel.send({ content: disableLinkPreviews(`Reminder for <@!${reminder.user_id}>: ${reminder.body}`), allowedMentions: { - users: [reminder.user_id], + users: [reminder.user_id as Snowflake], }, }); } diff --git a/backend/src/plugins/Roles/RolesPlugin.ts b/backend/src/plugins/Roles/RolesPlugin.ts index bcb8fb60..1b7dcfff 100644 --- a/backend/src/plugins/Roles/RolesPlugin.ts +++ b/backend/src/plugins/Roles/RolesPlugin.ts @@ -1,12 +1,12 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { PluginOptions } from "knub"; -import { ConfigSchema, RolesPluginType } from "./types"; import { GuildLogs } from "../../data/GuildLogs"; +import { trimPluginDescription } from "../../utils"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { AddRoleCmd } from "./commands/AddRoleCmd"; -import { RemoveRoleCmd } from "./commands/RemoveRoleCmd"; import { MassAddRoleCmd } from "./commands/MassAddRoleCmd"; import { MassRemoveRoleCmd } from "./commands/MassRemoveRoleCmd"; -import { trimPluginDescription } from "../../utils"; +import { RemoveRoleCmd } from "./commands/RemoveRoleCmd"; +import { ConfigSchema, RolesPluginType } from "./types"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/Roles/commands/AddRoleCmd.ts b/backend/src/plugins/Roles/commands/AddRoleCmd.ts index 49a69b08..66dc2096 100644 --- a/backend/src/plugins/Roles/commands/AddRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/AddRoleCmd.ts @@ -1,9 +1,10 @@ +import { GuildChannel } from "discord.js"; +import { memberToConfigAccessibleMember, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage, canActOn } from "../../../pluginUtils"; -import { rolesCmd } from "../types"; -import { resolveRoleId, stripObjectToScalars, verboseUserMention } from "../../../utils"; import { LogType } from "../../../data/LogType"; -import { GuildChannel } from "eris"; +import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { resolveRoleId, verboseUserMention } from "../../../utils"; +import { rolesCmd } from "../types"; export const AddRoleCmd = rolesCmd({ trigger: "addrole", @@ -34,7 +35,7 @@ export const AddRoleCmd = rolesCmd({ } // Sanity check: make sure the role is configured properly - const role = (msg.channel as GuildChannel).guild.roles.get(roleId); + const role = (msg.channel as GuildChannel).guild.roles.cache.get(roleId); if (!role) { pluginData.state.logs.log(LogType.BOT_ALERT, { body: `Unknown role configured for 'roles' plugin: ${roleId}`, @@ -43,19 +44,19 @@ export const AddRoleCmd = rolesCmd({ return; } - if (args.member.roles.includes(roleId)) { + if (args.member.roles.cache.has(roleId)) { sendErrorMessage(pluginData, msg.channel, "Member already has that role"); return; } pluginData.state.logs.ignoreLog(LogType.MEMBER_ROLE_ADD, args.member.id); - await args.member.addRole(roleId); + await args.member.roles.add(roleId); pluginData.state.logs.log(LogType.MEMBER_ROLE_ADD, { - member: stripObjectToScalars(args.member, ["user", "roles"]), + member: memberToConfigAccessibleMember(args.member), roles: role.name, - mod: stripObjectToScalars(msg.author), + mod: userToConfigAccessibleUser(msg.author), }); sendSuccessMessage( diff --git a/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts b/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts index d709edde..5f15a3f0 100644 --- a/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/MassAddRoleCmd.ts @@ -1,10 +1,11 @@ +import { GuildMember } from "discord.js"; +import { memberToConfigAccessibleMember, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, canActOn } from "../../../pluginUtils"; -import { rolesCmd } from "../types"; -import { resolveMember, resolveRoleId, stripObjectToScalars, successMessage } from "../../../utils"; import { LogType } from "../../../data/LogType"; import { logger } from "../../../logger"; -import { Member } from "eris"; +import { canActOn, sendErrorMessage } from "../../../pluginUtils"; +import { resolveMember, resolveRoleId, successMessage } from "../../../utils"; +import { rolesCmd } from "../types"; export const MassAddRoleCmd = rolesCmd({ trigger: "massaddrole", @@ -16,9 +17,9 @@ export const MassAddRoleCmd = rolesCmd({ }, async run({ message: msg, args, pluginData }) { - msg.channel.createMessage(`Resolving members...`); + msg.channel.send(`Resolving members...`); - const members: Member[] = []; + const members: GuildMember[] = []; const unknownMembers: string[] = []; for (const memberId of args.members) { const member = await resolveMember(pluginData.client, pluginData.guild, memberId); @@ -49,7 +50,7 @@ export const MassAddRoleCmd = rolesCmd({ return; } - const role = pluginData.guild.roles.get(roleId); + const role = pluginData.guild.roles.cache.get(roleId); if (!role) { pluginData.state.logs.log(LogType.BOT_ALERT, { body: `Unknown role configured for 'roles' plugin: ${roleId}`, @@ -58,12 +59,12 @@ export const MassAddRoleCmd = rolesCmd({ return; } - const membersWithoutTheRole = members.filter(m => !m.roles.includes(roleId)); + const membersWithoutTheRole = members.filter(m => !m.roles.cache.has(roleId)); let assigned = 0; const failed: string[] = []; const alreadyHadRole = members.length - membersWithoutTheRole.length; - msg.channel.createMessage( + msg.channel.send( `Adding role **${role.name}** to ${membersWithoutTheRole.length} ${ membersWithoutTheRole.length === 1 ? "member" : "members" }...`, @@ -72,11 +73,11 @@ export const MassAddRoleCmd = rolesCmd({ for (const member of membersWithoutTheRole) { try { pluginData.state.logs.ignoreLog(LogType.MEMBER_ROLE_ADD, member.id); - await member.addRole(roleId); + await member.roles.add(roleId); pluginData.state.logs.log(LogType.MEMBER_ROLE_ADD, { - member: stripObjectToScalars(member, ["user", "roles"]), + member: memberToConfigAccessibleMember(member), roles: role.name, - mod: stripObjectToScalars(msg.author), + mod: userToConfigAccessibleUser(msg.author), }); assigned++; } catch (e) { @@ -98,6 +99,6 @@ export const MassAddRoleCmd = rolesCmd({ resultMessage += `\nUnknown members: ${unknownMembers.join(", ")}`; } - msg.channel.createMessage(successMessage(resultMessage)); + msg.channel.send(successMessage(resultMessage)); }, }); diff --git a/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts b/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts index 8253b4ed..357f8049 100644 --- a/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/MassRemoveRoleCmd.ts @@ -1,10 +1,11 @@ +import { GuildMember } from "discord.js"; +import { memberToConfigAccessibleMember, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, canActOn } from "../../../pluginUtils"; -import { rolesCmd } from "../types"; -import { resolveMember, stripObjectToScalars, successMessage, resolveRoleId } from "../../../utils"; import { LogType } from "../../../data/LogType"; import { logger } from "../../../logger"; -import { Member } from "eris"; +import { canActOn, sendErrorMessage } from "../../../pluginUtils"; +import { resolveMember, resolveRoleId, successMessage } from "../../../utils"; +import { rolesCmd } from "../types"; export const MassRemoveRoleCmd = rolesCmd({ trigger: "massremoverole", @@ -16,9 +17,9 @@ export const MassRemoveRoleCmd = rolesCmd({ }, async run({ message: msg, args, pluginData }) { - msg.channel.createMessage(`Resolving members...`); + msg.channel.send(`Resolving members...`); - const members: Member[] = []; + const members: GuildMember[] = []; const unknownMembers: string[] = []; for (const memberId of args.members) { const member = await resolveMember(pluginData.client, pluginData.guild, memberId); @@ -49,7 +50,7 @@ export const MassRemoveRoleCmd = rolesCmd({ return; } - const role = pluginData.guild.roles.get(roleId); + const role = pluginData.guild.roles.cache.get(roleId); if (!role) { pluginData.state.logs.log(LogType.BOT_ALERT, { body: `Unknown role configured for 'roles' plugin: ${roleId}`, @@ -58,12 +59,12 @@ export const MassRemoveRoleCmd = rolesCmd({ return; } - const membersWithTheRole = members.filter(m => m.roles.includes(roleId)); + const membersWithTheRole = members.filter(m => m.roles.cache.has(roleId)); let assigned = 0; const failed: string[] = []; const didNotHaveRole = members.length - membersWithTheRole.length; - msg.channel.createMessage( + msg.channel.send( `Removing role **${role.name}** from ${membersWithTheRole.length} ${ membersWithTheRole.length === 1 ? "member" : "members" }...`, @@ -72,11 +73,11 @@ export const MassRemoveRoleCmd = rolesCmd({ for (const member of membersWithTheRole) { try { pluginData.state.logs.ignoreLog(LogType.MEMBER_ROLE_REMOVE, member.id); - await member.removeRole(roleId); + await member.roles.remove(roleId); pluginData.state.logs.log(LogType.MEMBER_ROLE_REMOVE, { - member: stripObjectToScalars(member, ["user", "roles"]), + member: memberToConfigAccessibleMember(member), roles: role.name, - mod: stripObjectToScalars(msg.author), + mod: userToConfigAccessibleUser(msg.author), }); assigned++; } catch (e) { @@ -98,6 +99,6 @@ export const MassRemoveRoleCmd = rolesCmd({ resultMessage += `\nUnknown members: ${unknownMembers.join(", ")}`; } - msg.channel.createMessage(successMessage(resultMessage)); + msg.channel.send(successMessage(resultMessage)); }, }); diff --git a/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts b/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts index 107b88e4..fdab1e23 100644 --- a/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts +++ b/backend/src/plugins/Roles/commands/RemoveRoleCmd.ts @@ -1,9 +1,10 @@ +import { GuildChannel } from "discord.js"; +import { memberToConfigAccessibleMember, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, sendSuccessMessage, canActOn } from "../../../pluginUtils"; -import { rolesCmd } from "../types"; -import { GuildChannel } from "eris"; import { LogType } from "../../../data/LogType"; -import { stripObjectToScalars, verboseUserMention, resolveRoleId } from "../../../utils"; +import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { resolveRoleId, verboseUserMention } from "../../../utils"; +import { rolesCmd } from "../types"; export const RemoveRoleCmd = rolesCmd({ trigger: "removerole", @@ -34,7 +35,7 @@ export const RemoveRoleCmd = rolesCmd({ } // Sanity check: make sure the role is configured properly - const role = (msg.channel as GuildChannel).guild.roles.get(roleId); + const role = (msg.channel as GuildChannel).guild.roles.cache.get(roleId); if (!role) { pluginData.state.logs.log(LogType.BOT_ALERT, { body: `Unknown role configured for 'roles' plugin: ${roleId}`, @@ -43,19 +44,19 @@ export const RemoveRoleCmd = rolesCmd({ return; } - if (!args.member.roles.includes(roleId)) { + if (!args.member.roles.cache.has(roleId)) { sendErrorMessage(pluginData, msg.channel, "Member doesn't have that role"); return; } pluginData.state.logs.ignoreLog(LogType.MEMBER_ROLE_REMOVE, args.member.id); - await args.member.removeRole(roleId); + await args.member.roles.remove(roleId); pluginData.state.logs.log(LogType.MEMBER_ROLE_REMOVE, { - member: stripObjectToScalars(args.member, ["user", "roles"]), + member: memberToConfigAccessibleMember(args.member), roles: role.name, - mod: stripObjectToScalars(msg.author), + mod: userToConfigAccessibleUser(msg.author), }); sendSuccessMessage( diff --git a/backend/src/plugins/SelfGrantableRoles/SelfGrantableRolesPlugin.ts b/backend/src/plugins/SelfGrantableRoles/SelfGrantableRolesPlugin.ts index b73488fd..981776fc 100644 --- a/backend/src/plugins/SelfGrantableRoles/SelfGrantableRolesPlugin.ts +++ b/backend/src/plugins/SelfGrantableRoles/SelfGrantableRolesPlugin.ts @@ -1,10 +1,10 @@ import { CooldownManager, PluginOptions } from "knub"; -import { SelfGrantableRolesPluginType, ConfigSchema, defaultSelfGrantableRoleEntry } from "./types"; -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { trimPluginDescription } from "../../utils"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { RoleAddCmd } from "./commands/RoleAddCmd"; -import { RoleRemoveCmd } from "./commands/RoleRemoveCmd"; import { RoleHelpCmd } from "./commands/RoleHelpCmd"; +import { RoleRemoveCmd } from "./commands/RoleRemoveCmd"; +import { ConfigSchema, defaultSelfGrantableRoleEntry, SelfGrantableRolesPluginType } from "./types"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts b/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts index 34c660a7..0568ea41 100644 --- a/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts +++ b/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts @@ -1,12 +1,12 @@ -import { selfGrantableRolesCmd } from "../types"; +import { Role, Snowflake } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { getApplyingEntries } from "../util/getApplyingEntries"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { splitRoleNames } from "../util/splitRoleNames"; -import { normalizeRoleNames } from "../util/normalizeRoleNames"; -import { findMatchingRoles } from "../util/findMatchingRoles"; -import { Role } from "eris"; import { memberRolesLock } from "../../../utils/lockNameHelpers"; +import { selfGrantableRolesCmd } from "../types"; +import { findMatchingRoles } from "../util/findMatchingRoles"; +import { getApplyingEntries } from "../util/getApplyingEntries"; +import { normalizeRoleNames } from "../util/normalizeRoleNames"; +import { splitRoleNames } from "../util/splitRoleNames"; export const RoleAddCmd = selfGrantableRolesCmd({ trigger: ["role", "role add"], @@ -31,7 +31,7 @@ export const RoleAddCmd = selfGrantableRolesCmd({ const hasUnknownRoles = matchedRoleIds.length !== roleNames.length; const rolesToAdd: Map = Array.from(matchedRoleIds.values()) - .map(id => pluginData.guild.roles.get(id)!) + .map(id => pluginData.guild.roles.cache.get(id as Snowflake)!) .filter(Boolean) .reduce((map, role) => { map.set(role.id, role); @@ -49,7 +49,7 @@ export const RoleAddCmd = selfGrantableRolesCmd({ } // Grant the roles - const newRoleIds = new Set([...rolesToAdd.keys(), ...msg.member.roles]); + const newRoleIds = new Set([...rolesToAdd.keys(), ...msg.member.roles.cache.keys()]); // Remove extra roles (max_roles) for each entry const skipped: Set = new Set(); @@ -68,10 +68,10 @@ export const RoleAddCmd = selfGrantableRolesCmd({ newRoleIds.delete(roleId); rolesToAdd.delete(roleId); - if (msg.member.roles.includes(roleId)) { - removed.add(pluginData.guild.roles.get(roleId)!); + if (msg.member.roles.cache.has(roleId as Snowflake)) { + removed.add(pluginData.guild.roles.cache.get(roleId as Snowflake)!); } else { - skipped.add(pluginData.guild.roles.get(roleId)!); + skipped.add(pluginData.guild.roles.cache.get(roleId as Snowflake)!); } } } @@ -80,7 +80,7 @@ export const RoleAddCmd = selfGrantableRolesCmd({ try { await msg.member.edit({ - roles: Array.from(newRoleIds), + roles: Array.from(newRoleIds) as Snowflake[], }); } catch { sendErrorMessage( diff --git a/backend/src/plugins/SelfGrantableRoles/commands/RoleHelpCmd.ts b/backend/src/plugins/SelfGrantableRoles/commands/RoleHelpCmd.ts index 41d96d55..b1bf6899 100644 --- a/backend/src/plugins/SelfGrantableRoles/commands/RoleHelpCmd.ts +++ b/backend/src/plugins/SelfGrantableRoles/commands/RoleHelpCmd.ts @@ -1,5 +1,5 @@ -import { selfGrantableRolesCmd } from "../types"; import { asSingleLine, trimLines } from "../../../utils"; +import { selfGrantableRolesCmd } from "../types"; import { getApplyingEntries } from "../util/getApplyingEntries"; export const RoleHelpCmd = selfGrantableRolesCmd({ @@ -47,6 +47,6 @@ export const RoleHelpCmd = selfGrantableRolesCmd({ color: parseInt("42bff4", 16), }; - msg.channel.createMessage({ embed: helpEmbed }); + msg.channel.send({ embeds: [helpEmbed] }); }, }); diff --git a/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts b/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts index 3462d86f..0ba51766 100644 --- a/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts +++ b/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts @@ -1,11 +1,12 @@ -import { selfGrantableRolesCmd } from "../types"; +import { Snowflake } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { getApplyingEntries } from "../util/getApplyingEntries"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { splitRoleNames } from "../util/splitRoleNames"; -import { normalizeRoleNames } from "../util/normalizeRoleNames"; -import { findMatchingRoles } from "../util/findMatchingRoles"; import { memberRolesLock } from "../../../utils/lockNameHelpers"; +import { selfGrantableRolesCmd } from "../types"; +import { findMatchingRoles } from "../util/findMatchingRoles"; +import { getApplyingEntries } from "../util/getApplyingEntries"; +import { normalizeRoleNames } from "../util/normalizeRoleNames"; +import { splitRoleNames } from "../util/splitRoleNames"; export const RoleRemoveCmd = selfGrantableRolesCmd({ trigger: "role remove", @@ -27,12 +28,14 @@ export const RoleRemoveCmd = selfGrantableRolesCmd({ const roleNames = normalizeRoleNames(splitRoleNames(args.roleNames)); const matchedRoleIds = findMatchingRoles(roleNames, applyingEntries); - const rolesToRemove = Array.from(matchedRoleIds.values()).map(id => pluginData.guild.roles.get(id)!); + const rolesToRemove = Array.from(matchedRoleIds.values()).map( + id => pluginData.guild.roles.cache.get(id as Snowflake)!, + ); const roleIdsToRemove = rolesToRemove.map(r => r.id); // Remove the roles if (rolesToRemove.length) { - const newRoleIds = msg.member.roles.filter(roleId => !roleIdsToRemove.includes(roleId)); + const newRoleIds = msg.member.roles.cache.filter(role => !roleIdsToRemove.includes(role.id)); try { await msg.member.edit({ diff --git a/backend/src/plugins/SelfGrantableRoles/types.ts b/backend/src/plugins/SelfGrantableRoles/types.ts index 8423acd4..dbebcf9f 100644 --- a/backend/src/plugins/SelfGrantableRoles/types.ts +++ b/backend/src/plugins/SelfGrantableRoles/types.ts @@ -1,5 +1,5 @@ import * as t from "io-ts"; -import { BasePluginType, typedGuildCommand, CooldownManager } from "knub"; +import { BasePluginType, CooldownManager, typedGuildCommand } from "knub"; const RoleMap = t.record(t.string, t.array(t.string)); diff --git a/backend/src/plugins/SelfGrantableRoles/util/getApplyingEntries.ts b/backend/src/plugins/SelfGrantableRoles/util/getApplyingEntries.ts index 41e8b87e..65495be4 100644 --- a/backend/src/plugins/SelfGrantableRoles/util/getApplyingEntries.ts +++ b/backend/src/plugins/SelfGrantableRoles/util/getApplyingEntries.ts @@ -1,5 +1,5 @@ -import { TSelfGrantableRoleEntry, SelfGrantableRolesPluginType } from "../types"; import { GuildPluginData } from "knub"; +import { SelfGrantableRolesPluginType, TSelfGrantableRoleEntry } from "../types"; export async function getApplyingEntries( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Slowmode/SlowmodePlugin.ts b/backend/src/plugins/Slowmode/SlowmodePlugin.ts index 79190820..f7dd06a3 100644 --- a/backend/src/plugins/Slowmode/SlowmodePlugin.ts +++ b/backend/src/plugins/Slowmode/SlowmodePlugin.ts @@ -1,18 +1,18 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { PluginOptions } from "knub"; -import { ConfigSchema, SlowmodePluginType } from "./types"; -import { GuildSlowmodes } from "../../data/GuildSlowmodes"; -import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildLogs } from "../../data/GuildLogs"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { GuildSlowmodes } from "../../data/GuildSlowmodes"; import { SECONDS } from "../../utils"; -import { onMessageCreate } from "./util/onMessageCreate"; -import { clearExpiredSlowmodes } from "./util/clearExpiredSlowmodes"; -import { SlowmodeDisableCmd } from "./commands/SlowmodeDisableCmd"; -import { SlowmodeClearCmd } from "./commands/SlowmodeClearCmd"; -import { SlowmodeListCmd } from "./commands/SlowmodeListCmd"; -import { SlowmodeGetCmd } from "./commands/SlowmodeGetCmd"; -import { SlowmodeSetCmd } from "./commands/SlowmodeSetCmd"; import { LogsPlugin } from "../Logs/LogsPlugin"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import { SlowmodeClearCmd } from "./commands/SlowmodeClearCmd"; +import { SlowmodeDisableCmd } from "./commands/SlowmodeDisableCmd"; +import { SlowmodeGetCmd } from "./commands/SlowmodeGetCmd"; +import { SlowmodeListCmd } from "./commands/SlowmodeListCmd"; +import { SlowmodeSetCmd } from "./commands/SlowmodeSetCmd"; +import { ConfigSchema, SlowmodePluginType } from "./types"; +import { clearExpiredSlowmodes } from "./util/clearExpiredSlowmodes"; +import { onMessageCreate } from "./util/onMessageCreate"; const BOT_SLOWMODE_CLEAR_INTERVAL = 60 * SECONDS; @@ -66,6 +66,7 @@ export const SlowmodePlugin = zeppelinGuildPlugin()({ afterLoad(pluginData) { const { state } = pluginData; + state.serverLogs = new GuildLogs(pluginData.guild.id); state.clearInterval = setInterval(() => clearExpiredSlowmodes(pluginData), BOT_SLOWMODE_CLEAR_INTERVAL); state.onMessageCreateFn = msg => onMessageCreate(pluginData, msg); diff --git a/backend/src/plugins/Slowmode/commands/SlowmodeClearCmd.ts b/backend/src/plugins/Slowmode/commands/SlowmodeClearCmd.ts index c650cd8c..c9a31ddd 100644 --- a/backend/src/plugins/Slowmode/commands/SlowmodeClearCmd.ts +++ b/backend/src/plugins/Slowmode/commands/SlowmodeClearCmd.ts @@ -1,11 +1,13 @@ +import { Util } from "discord.js"; +import { ChannelTypeStrings } from "src/types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { asSingleLine } from "../../../utils"; +import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions"; +import { missingPermissionError } from "../../../utils/missingPermissionError"; +import { BOT_SLOWMODE_CLEAR_PERMISSIONS } from "../requiredPermissions"; import { slowmodeCmd } from "../types"; import { clearBotSlowmodeFromUserId } from "../util/clearBotSlowmodeFromUserId"; -import { asSingleLine, disableInlineCode } from "../../../utils"; -import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions"; -import { BOT_SLOWMODE_CLEAR_PERMISSIONS } from "../requiredPermissions"; -import { missingPermissionError } from "../../../utils/missingPermissionError"; export const SlowmodeClearCmd = slowmodeCmd({ trigger: ["slowmode clear", "slowmode c"], @@ -25,7 +27,7 @@ export const SlowmodeClearCmd = slowmodeCmd({ return; } - const me = pluginData.guild.members.get(pluginData.client.user.id)!; + const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!; const missingPermissions = getMissingChannelPermissions(me, args.channel, BOT_SLOWMODE_CLEAR_PERMISSIONS); if (missingPermissions) { sendErrorMessage( @@ -37,23 +39,31 @@ export const SlowmodeClearCmd = slowmodeCmd({ } try { - await clearBotSlowmodeFromUserId(pluginData, args.channel, args.user.id, args.force); + if (args.channel.type === ChannelTypeStrings.TEXT) { + await clearBotSlowmodeFromUserId(pluginData, args.channel, args.user.id, args.force); + } else { + sendErrorMessage( + pluginData, + msg.channel, + asSingleLine(` + Failed to clear slowmode from **${args.user.tag}** in <#${args.channel.id}>: + Threads cannot have Bot Slowmode + `), + ); + return; + } } catch (e) { sendErrorMessage( pluginData, msg.channel, asSingleLine(` - Failed to clear slowmode from **${args.user.username}#${args.user.discriminator}** in <#${args.channel.id}>: - \`${disableInlineCode(e.message)}\` + Failed to clear slowmode from **${args.user.tag}** in <#${args.channel.id}>: + \`${Util.escapeInlineCode(e.message)}\` `), ); return; } - sendSuccessMessage( - pluginData, - msg.channel, - `Slowmode cleared from **${args.user.username}#${args.user.discriminator}** in <#${args.channel.id}>`, - ); + sendSuccessMessage(pluginData, msg.channel, `Slowmode cleared from **${args.user.tag}** in <#${args.channel.id}>`); }, }); diff --git a/backend/src/plugins/Slowmode/commands/SlowmodeGetCmd.ts b/backend/src/plugins/Slowmode/commands/SlowmodeGetCmd.ts index e9ffe108..e1302464 100644 --- a/backend/src/plugins/Slowmode/commands/SlowmodeGetCmd.ts +++ b/backend/src/plugins/Slowmode/commands/SlowmodeGetCmd.ts @@ -1,7 +1,7 @@ +import { TextChannel } from "discord.js"; +import humanizeDuration from "humanize-duration"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { slowmodeCmd } from "../types"; -import { TextChannel } from "eris"; -import humanizeDuration from "humanize-duration"; export const SlowmodeGetCmd = slowmodeCmd({ trigger: "slowmode", @@ -27,11 +27,11 @@ export const SlowmodeGetCmd = slowmodeCmd({ } if (currentSlowmode) { - const humanized = humanizeDuration(channel.rateLimitPerUser * 1000); + const humanized = humanizeDuration(currentSlowmode * 1000); const slowmodeType = isNative ? "native" : "bot-maintained"; - msg.channel.createMessage(`The current slowmode of <#${channel.id}> is **${humanized}** (${slowmodeType})`); + msg.channel.send(`The current slowmode of <#${channel.id}> is **${humanized}** (${slowmodeType})`); } else { - msg.channel.createMessage("Channel is not on slowmode"); + msg.channel.send("Channel is not on slowmode"); } }, }); diff --git a/backend/src/plugins/Slowmode/commands/SlowmodeListCmd.ts b/backend/src/plugins/Slowmode/commands/SlowmodeListCmd.ts index 136ff277..0ef7733c 100644 --- a/backend/src/plugins/Slowmode/commands/SlowmodeListCmd.ts +++ b/backend/src/plugins/Slowmode/commands/SlowmodeListCmd.ts @@ -1,8 +1,8 @@ -import { slowmodeCmd } from "../types"; -import { GuildChannel, TextChannel } from "eris"; +import { GuildChannel, TextChannel } from "discord.js"; +import humanizeDuration from "humanize-duration"; import { createChunkedMessage } from "knub/dist/helpers"; import { errorMessage } from "../../../utils"; -import humanizeDuration from "humanize-duration"; +import { slowmodeCmd } from "../types"; export const SlowmodeListCmd = slowmodeCmd({ trigger: ["slowmode list", "slowmode l", "slowmodes"], @@ -12,7 +12,7 @@ export const SlowmodeListCmd = slowmodeCmd({ const channels = pluginData.guild.channels; const slowmodes: Array<{ channel: GuildChannel; seconds: number; native: boolean }> = []; - for (const channel of channels.values()) { + for (const channel of channels.cache.values()) { if (!(channel instanceof TextChannel)) continue; // Bot slowmode @@ -40,7 +40,7 @@ export const SlowmodeListCmd = slowmodeCmd({ createChunkedMessage(msg.channel, lines.join("\n")); } else { - msg.channel.createMessage(errorMessage("No active slowmodes!")); + msg.channel.send(errorMessage("No active slowmodes!")); } }, }); diff --git a/backend/src/plugins/Slowmode/commands/SlowmodeSetCmd.ts b/backend/src/plugins/Slowmode/commands/SlowmodeSetCmd.ts index 7039b7e7..3b316c6f 100644 --- a/backend/src/plugins/Slowmode/commands/SlowmodeSetCmd.ts +++ b/backend/src/plugins/Slowmode/commands/SlowmodeSetCmd.ts @@ -1,14 +1,15 @@ -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { slowmodeCmd } from "../types"; -import { TextChannel } from "eris"; +import { Permissions, TextChannel, ThreadChannel, Util } from "discord.js"; import humanizeDuration from "humanize-duration"; +import { ChannelTypeStrings } from "src/types"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { asSingleLine, DAYS, disableInlineCode, HOURS, MINUTES } from "../../../utils"; -import { disableBotSlowmodeForChannel } from "../util/disableBotSlowmodeForChannel"; -import { actualDisableSlowmodeCmd } from "../util/actualDisableSlowmodeCmd"; +import { asSingleLine, DAYS, HOURS, MINUTES } from "../../../utils"; import { getMissingPermissions } from "../../../utils/getMissingPermissions"; import { missingPermissionError } from "../../../utils/missingPermissionError"; import { BOT_SLOWMODE_PERMISSIONS, NATIVE_SLOWMODE_PERMISSIONS } from "../requiredPermissions"; +import { slowmodeCmd } from "../types"; +import { actualDisableSlowmodeCmd } from "../util/actualDisableSlowmodeCmd"; +import { disableBotSlowmodeForChannel } from "../util/disableBotSlowmodeForChannel"; const MAX_NATIVE_SLOWMODE = 6 * HOURS; // 6 hours const MAX_BOT_SLOWMODE = DAYS * 365 * 100; // 100 years @@ -38,7 +39,7 @@ export const SlowmodeSetCmd = slowmodeCmd({ ], async run({ message: msg, args, pluginData }) { - const channel: TextChannel = args.channel || msg.channel; + const channel: TextChannel | ThreadChannel = args.channel || msg.channel; if (args.time === 0) { // Workaround until we can call SlowmodeDisableCmd from here @@ -84,10 +85,13 @@ export const SlowmodeSetCmd = slowmodeCmd({ } // Verify permissions - const channelPermissions = channel.permissionsOf(pluginData.client.user.id); + const channelPermissions = channel.permissionsFor(pluginData.client.user!.id); if (mode === "native") { - const missingPermissions = getMissingPermissions(channelPermissions, NATIVE_SLOWMODE_PERMISSIONS); + const missingPermissions = getMissingPermissions( + channelPermissions ?? new Permissions(), + NATIVE_SLOWMODE_PERMISSIONS, + ); if (missingPermissions) { sendErrorMessage( pluginData, @@ -99,7 +103,10 @@ export const SlowmodeSetCmd = slowmodeCmd({ } if (mode === "bot") { - const missingPermissions = getMissingPermissions(channelPermissions, BOT_SLOWMODE_PERMISSIONS); + const missingPermissions = getMissingPermissions( + channelPermissions ?? new Permissions(), + BOT_SLOWMODE_PERMISSIONS, + ); if (missingPermissions) { sendErrorMessage( pluginData, @@ -116,7 +123,7 @@ export const SlowmodeSetCmd = slowmodeCmd({ if (mode === "native") { // If there is an existing bot-maintained slowmode, disable that first const existingBotSlowmode = await pluginData.state.slowmodes.getChannelSlowmode(channel.id); - if (existingBotSlowmode) { + if (existingBotSlowmode && channel.type === ChannelTypeStrings.TEXT) { await disableBotSlowmodeForChannel(pluginData, channel); } @@ -126,7 +133,7 @@ export const SlowmodeSetCmd = slowmodeCmd({ rateLimitPerUser: rateLimitSeconds, }); } catch (e) { - sendErrorMessage(pluginData, msg.channel, `Failed to set native slowmode: ${disableInlineCode(e.message)}`); + sendErrorMessage(pluginData, msg.channel, `Failed to set native slowmode: ${Util.escapeInlineCode(e.message)}`); return; } } else { diff --git a/backend/src/plugins/Slowmode/requiredPermissions.ts b/backend/src/plugins/Slowmode/requiredPermissions.ts index 7b1b52f5..ea9065be 100644 --- a/backend/src/plugins/Slowmode/requiredPermissions.ts +++ b/backend/src/plugins/Slowmode/requiredPermissions.ts @@ -1,8 +1,8 @@ -import { Constants } from "eris"; +import { Permissions } from "discord.js"; -const p = Constants.Permissions; +const p = Permissions.FLAGS; -export const NATIVE_SLOWMODE_PERMISSIONS = p.readMessages | p.manageChannels; -export const BOT_SLOWMODE_PERMISSIONS = p.readMessages | p.manageRoles | p.manageMessages; -export const BOT_SLOWMODE_CLEAR_PERMISSIONS = p.readMessages | p.manageRoles; -export const BOT_SLOWMODE_DISABLE_PERMISSIONS = p.readMessages | p.manageRoles; +export const NATIVE_SLOWMODE_PERMISSIONS = p.VIEW_CHANNEL | p.MANAGE_CHANNELS; +export const BOT_SLOWMODE_PERMISSIONS = p.VIEW_CHANNEL | p.MANAGE_ROLES | p.MANAGE_MESSAGES; +export const BOT_SLOWMODE_CLEAR_PERMISSIONS = p.VIEW_CHANNEL | p.MANAGE_ROLES; +export const BOT_SLOWMODE_DISABLE_PERMISSIONS = p.VIEW_CHANNEL | p.MANAGE_ROLES; diff --git a/backend/src/plugins/Slowmode/types.ts b/backend/src/plugins/Slowmode/types.ts index bfd71e4c..7679df34 100644 --- a/backend/src/plugins/Slowmode/types.ts +++ b/backend/src/plugins/Slowmode/types.ts @@ -1,8 +1,8 @@ import * as t from "io-ts"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub"; -import { GuildSlowmodes } from "../../data/GuildSlowmodes"; -import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildLogs } from "../../data/GuildLogs"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { GuildSlowmodes } from "../../data/GuildSlowmodes"; export const ConfigSchema = t.type({ use_native_slowmode: t.boolean, @@ -19,6 +19,7 @@ export interface SlowmodePluginType extends BasePluginType { savedMessages: GuildSavedMessages; logs: GuildLogs; clearInterval: NodeJS.Timeout; + serverLogs: GuildLogs; onMessageCreateFn; }; diff --git a/backend/src/plugins/Slowmode/util/actualDisableSlowmodeCmd.ts b/backend/src/plugins/Slowmode/util/actualDisableSlowmodeCmd.ts index cd29bc08..09565bae 100644 --- a/backend/src/plugins/Slowmode/util/actualDisableSlowmodeCmd.ts +++ b/backend/src/plugins/Slowmode/util/actualDisableSlowmodeCmd.ts @@ -1,32 +1,32 @@ -import { Message } from "eris"; +import { Message, TextChannel } from "discord.js"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { disableBotSlowmodeForChannel } from "./disableBotSlowmodeForChannel"; import { noop } from "../../../utils"; import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions"; -import { BOT_SLOWMODE_DISABLE_PERMISSIONS } from "../requiredPermissions"; import { missingPermissionError } from "../../../utils/missingPermissionError"; +import { BOT_SLOWMODE_DISABLE_PERMISSIONS } from "../requiredPermissions"; +import { disableBotSlowmodeForChannel } from "./disableBotSlowmodeForChannel"; export async function actualDisableSlowmodeCmd(msg: Message, args, pluginData) { const botSlowmode = await pluginData.state.slowmodes.getChannelSlowmode(args.channel.id); const hasNativeSlowmode = args.channel.rateLimitPerUser; if (!botSlowmode && hasNativeSlowmode === 0) { - sendErrorMessage(pluginData, msg.channel, "Channel is not on slowmode!"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "Channel is not on slowmode!"); return; } - const me = pluginData.guild.members.get(pluginData.client.user.id); + const me = pluginData.guild.members.cache.get(pluginData.client.user!.id); const missingPermissions = getMissingChannelPermissions(me, args.channel, BOT_SLOWMODE_DISABLE_PERMISSIONS); if (missingPermissions) { sendErrorMessage( pluginData, - msg.channel, + msg.channel as TextChannel, `Unable to disable slowmode. ${missingPermissionError(missingPermissions)}`, ); return; } - const initMsg = await msg.channel.createMessage("Disabling slowmode..."); + const initMsg = await msg.channel.send("Disabling slowmode..."); // Disable bot-maintained slowmode let failedUsers: string[] = []; @@ -43,11 +43,11 @@ export async function actualDisableSlowmodeCmd(msg: Message, args, pluginData) { if (failedUsers.length) { sendSuccessMessage( pluginData, - msg.channel, + msg.channel as TextChannel, `Slowmode disabled! Failed to clear slowmode from the following users:\n\n<@!${failedUsers.join(">\n<@!")}>`, ); } else { - sendSuccessMessage(pluginData, msg.channel, "Slowmode disabled!"); + sendSuccessMessage(pluginData, msg.channel as TextChannel, "Slowmode disabled!"); initMsg.delete().catch(noop); } } diff --git a/backend/src/plugins/Slowmode/util/applyBotSlowmodeToUserId.ts b/backend/src/plugins/Slowmode/util/applyBotSlowmodeToUserId.ts index 356f0bdb..3a29a711 100644 --- a/backend/src/plugins/Slowmode/util/applyBotSlowmodeToUserId.ts +++ b/backend/src/plugins/Slowmode/util/applyBotSlowmodeToUserId.ts @@ -1,9 +1,10 @@ -import { SlowmodePluginType } from "../types"; +import { GuildChannel, Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { Constants, GuildChannel, TextChannel } from "eris"; -import { isDiscordRESTError, stripObjectToScalars, UnknownUser } from "../../../utils"; +import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { LogType } from "../../../data/LogType"; import { logger } from "../../../logger"; +import { isDiscordAPIError, UnknownUser } from "../../../utils"; +import { SlowmodePluginType } from "../types"; export async function applyBotSlowmodeToUserId( pluginData: GuildPluginData, @@ -11,29 +12,31 @@ export async function applyBotSlowmodeToUserId( userId: string, ) { // Deny sendMessage permission from the user. If there are existing permission overwrites, take those into account. - const existingOverride = channel.permissionOverwrites.get(userId); - const newDeniedPermissions = (existingOverride ? existingOverride.deny : 0n) | Constants.Permissions.sendMessages; - const newAllowedPermissions = (existingOverride ? existingOverride.allow : 0n) & ~Constants.Permissions.sendMessages; - + const existingOverride = channel.permissionOverwrites.resolve(userId as Snowflake); try { - await channel.editPermission(userId, newAllowedPermissions, newDeniedPermissions, "member"); + pluginData.state.serverLogs.ignoreLog(LogType.CHANNEL_UPDATE, channel.id, 5 * 1000); + if (existingOverride) { + await existingOverride.edit({ SEND_MESSAGES: false }); + } else { + await channel.permissionOverwrites.create(userId as Snowflake, { SEND_MESSAGES: false }, { type: 1 }); + } } catch (e) { - const user = pluginData.client.users.get(userId) || new UnknownUser({ id: userId }); + const user = await pluginData.client.users.fetch(userId as Snowflake).catch(() => new UnknownUser({ id: userId })); - if (isDiscordRESTError(e) && e.code === 50013) { + if (isDiscordAPIError(e) && e.code === 50013) { logger.warn( `Missing permissions to apply bot slowmode to user ${userId} on channel ${channel.name} (${channel.id}) on server ${pluginData.guild.name} (${pluginData.guild.id})`, ); pluginData.state.logs.log(LogType.BOT_ALERT, { body: `Missing permissions to apply bot slowmode to {userMention(user)} in {channelMention(channel)}`, - user: stripObjectToScalars(user), - channel: stripObjectToScalars(channel), + user: userToConfigAccessibleUser(user), + channel: channelToConfigAccessibleChannel(channel), }); } else { pluginData.state.logs.log(LogType.BOT_ALERT, { body: `Failed to apply bot slowmode to {userMention(user)} in {channelMention(channel)}`, - user: stripObjectToScalars(user), - channel: stripObjectToScalars(channel), + user: userToConfigAccessibleUser(user), + channel: channelToConfigAccessibleChannel(channel), }); throw e; } diff --git a/backend/src/plugins/Slowmode/util/clearBotSlowmodeFromUserId.ts b/backend/src/plugins/Slowmode/util/clearBotSlowmodeFromUserId.ts index 7a33d4aa..b86862ae 100644 --- a/backend/src/plugins/Slowmode/util/clearBotSlowmodeFromUserId.ts +++ b/backend/src/plugins/Slowmode/util/clearBotSlowmodeFromUserId.ts @@ -1,10 +1,11 @@ +import { Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; +import { LogType } from "../../../data/LogType"; import { SlowmodePluginType } from "../types"; -import { GuildChannel, TextChannel } from "eris"; export async function clearBotSlowmodeFromUserId( pluginData: GuildPluginData, - channel: GuildChannel & TextChannel, + channel: TextChannel, userId: string, force = false, ) { @@ -13,7 +14,8 @@ export async function clearBotSlowmodeFromUserId( // Previously we diffed the overrides so we could clear the "send messages" override without touching other // overrides. Unfortunately, it seems that was a bit buggy - we didn't always receive the event for the changed // overrides and then we also couldn't diff against them. For consistency's sake, we just delete the override now. - await channel.deletePermission(userId); + pluginData.state.serverLogs.ignoreLog(LogType.CHANNEL_UPDATE, channel.id, 3 * 1000); + await channel.permissionOverwrites.resolve(userId as Snowflake)?.delete(); } catch (e) { if (!force) { throw e; diff --git a/backend/src/plugins/Slowmode/util/clearExpiredSlowmodes.ts b/backend/src/plugins/Slowmode/util/clearExpiredSlowmodes.ts index 5bf650c2..f8490e4d 100644 --- a/backend/src/plugins/Slowmode/util/clearExpiredSlowmodes.ts +++ b/backend/src/plugins/Slowmode/util/clearExpiredSlowmodes.ts @@ -1,15 +1,16 @@ +import { GuildChannel, Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { SlowmodePluginType } from "../types"; +import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { LogType } from "../../../data/LogType"; import { logger } from "../../../logger"; -import { GuildChannel, TextChannel } from "eris"; -import { stripObjectToScalars, UnknownUser } from "../../../utils"; +import { UnknownUser } from "../../../utils"; +import { SlowmodePluginType } from "../types"; import { clearBotSlowmodeFromUserId } from "./clearBotSlowmodeFromUserId"; export async function clearExpiredSlowmodes(pluginData: GuildPluginData) { const expiredSlowmodeUsers = await pluginData.state.slowmodes.getExpiredSlowmodeUsers(); for (const user of expiredSlowmodeUsers) { - const channel = pluginData.guild.channels.get(user.channel_id); + const channel = pluginData.guild.channels.cache.get(user.channel_id as Snowflake); if (!channel) { await pluginData.state.slowmodes.clearSlowmodeUser(user.channel_id, user.user_id); continue; @@ -20,11 +21,14 @@ export async function clearExpiredSlowmodes(pluginData: GuildPluginData new UnknownUser({ id: user.user_id })); + pluginData.state.logs.log(LogType.BOT_ALERT, { body: `Failed to clear slowmode permissions from {userMention(user)} in {channelMention(channel)}`, - user: stripObjectToScalars(realUser), - channel: stripObjectToScalars(channel), + user: userToConfigAccessibleUser(await realUser), + channel: channelToConfigAccessibleChannel(channel), }); } } diff --git a/backend/src/plugins/Slowmode/util/disableBotSlowmodeForChannel.ts b/backend/src/plugins/Slowmode/util/disableBotSlowmodeForChannel.ts index 39b1ab86..43d6cb55 100644 --- a/backend/src/plugins/Slowmode/util/disableBotSlowmodeForChannel.ts +++ b/backend/src/plugins/Slowmode/util/disableBotSlowmodeForChannel.ts @@ -1,11 +1,11 @@ -import { GuildChannel, TextChannel } from "eris"; +import { TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; import { SlowmodePluginType } from "../types"; import { clearBotSlowmodeFromUserId } from "./clearBotSlowmodeFromUserId"; export async function disableBotSlowmodeForChannel( pluginData: GuildPluginData, - channel: GuildChannel & TextChannel, + channel: TextChannel, ) { // Disable channel slowmode await pluginData.state.slowmodes.deleteChannelSlowmode(channel.id); diff --git a/backend/src/plugins/Slowmode/util/onMessageCreate.ts b/backend/src/plugins/Slowmode/util/onMessageCreate.ts index 966ce429..097d10b1 100644 --- a/backend/src/plugins/Slowmode/util/onMessageCreate.ts +++ b/backend/src/plugins/Slowmode/util/onMessageCreate.ts @@ -1,21 +1,21 @@ -import { SavedMessage } from "../../../data/entities/SavedMessage"; -import { TextChannel } from "eris"; +import { Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { SlowmodePluginType } from "../types"; -import { resolveMember } from "../../../utils"; -import { applyBotSlowmodeToUserId } from "./applyBotSlowmodeToUserId"; -import { hasPermission } from "../../../pluginUtils"; -import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions"; -import { BOT_SLOWMODE_PERMISSIONS } from "../requiredPermissions"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { SavedMessage } from "../../../data/entities/SavedMessage"; import { LogType } from "../../../data/LogType"; -import { missingPermissionError } from "../../../utils/missingPermissionError"; +import { hasPermission } from "../../../pluginUtils"; +import { resolveMember } from "../../../utils"; +import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions"; import { messageLock } from "../../../utils/lockNameHelpers"; +import { missingPermissionError } from "../../../utils/missingPermissionError"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { BOT_SLOWMODE_PERMISSIONS } from "../requiredPermissions"; +import { SlowmodePluginType } from "../types"; +import { applyBotSlowmodeToUserId } from "./applyBotSlowmodeToUserId"; export async function onMessageCreate(pluginData: GuildPluginData, msg: SavedMessage) { if (msg.is_bot) return; - const channel = pluginData.guild.channels.get(msg.channel_id) as TextChannel; + const channel = pluginData.guild.channels.cache.get(msg.channel_id as Snowflake) as TextChannel; if (!channel) return; // Don't apply slowmode if the lock was interrupted earlier (e.g. the message was caught by word filters) @@ -36,7 +36,7 @@ export async function onMessageCreate(pluginData: GuildPluginData = { config: { @@ -60,8 +60,7 @@ export const SpamPlugin = zeppelinGuildPlugin()({ // prettier-ignore events: [ - SpamVoiceJoinEvt, - SpamVoiceSwitchEvt, + SpamVoiceStateUpdateEvt, ], beforeLoad(pluginData) { diff --git a/backend/src/plugins/Spam/events/SpamVoiceEvt.ts b/backend/src/plugins/Spam/events/SpamVoiceEvt.ts index ab99afd4..36aba638 100644 --- a/backend/src/plugins/Spam/events/SpamVoiceEvt.ts +++ b/backend/src/plugins/Spam/events/SpamVoiceEvt.ts @@ -1,37 +1,14 @@ -import { spamEvt, RecentActionType } from "../types"; +import { RecentActionType, spamEvt } from "../types"; import { logAndDetectOtherSpam } from "../util/logAndDetectOtherSpam"; -export const SpamVoiceJoinEvt = spamEvt({ - event: "voiceChannelJoin", +export const SpamVoiceStateUpdateEvt = spamEvt({ + event: "voiceStateUpdate", async listener(meta) { - const member = meta.args.member; - const channel = meta.args.newChannel; - - const config = await meta.pluginData.config.getMatchingConfig({ member, channelId: channel.id }); - const maxVoiceMoves = config.max_voice_moves; - if (maxVoiceMoves) { - logAndDetectOtherSpam( - meta.pluginData, - RecentActionType.VoiceChannelMove, - maxVoiceMoves, - member.id, - 1, - "0", - Date.now(), - null, - "too many voice channel moves", - ); - } - }, -}); - -export const SpamVoiceSwitchEvt = spamEvt({ - event: "voiceChannelSwitch", - - async listener(meta) { - const member = meta.args.member; - const channel = meta.args.newChannel; + const member = meta.args.newState.member; + if (!member) return; + const channel = meta.args.newState.channel; + if (!channel) return; const config = await meta.pluginData.config.getMatchingConfig({ member, channelId: channel.id }); const maxVoiceMoves = config.max_voice_moves; diff --git a/backend/src/plugins/Spam/types.ts b/backend/src/plugins/Spam/types.ts index 9d4e0567..68697f02 100644 --- a/backend/src/plugins/Spam/types.ts +++ b/backend/src/plugins/Spam/types.ts @@ -1,10 +1,10 @@ import * as t from "io-ts"; import { BasePluginType, typedGuildEventListener } from "knub"; -import { tNullable } from "../../utils"; -import { GuildLogs } from "../../data/GuildLogs"; import { GuildArchives } from "../../data/GuildArchives"; -import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { GuildLogs } from "../../data/GuildLogs"; import { GuildMutes } from "../../data/GuildMutes"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { tNullable } from "../../utils"; const BaseSingleSpamConfig = t.type({ interval: t.number, diff --git a/backend/src/plugins/Spam/util/addRecentAction.ts b/backend/src/plugins/Spam/util/addRecentAction.ts index ec9e1261..418702a8 100644 --- a/backend/src/plugins/Spam/util/addRecentAction.ts +++ b/backend/src/plugins/Spam/util/addRecentAction.ts @@ -1,5 +1,5 @@ import { GuildPluginData } from "knub"; -import { SpamPluginType, RecentActionType } from "../types"; +import { RecentActionType, SpamPluginType } from "../types"; export function addRecentAction( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Spam/util/clearRecentUserActions.ts b/backend/src/plugins/Spam/util/clearRecentUserActions.ts index 839ac107..cfaf7b53 100644 --- a/backend/src/plugins/Spam/util/clearRecentUserActions.ts +++ b/backend/src/plugins/Spam/util/clearRecentUserActions.ts @@ -1,5 +1,5 @@ -import { RecentActionType, SpamPluginType } from "../types"; import { GuildPluginData } from "knub"; +import { RecentActionType, SpamPluginType } from "../types"; export function clearRecentUserActions( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Spam/util/getRecentActionCount.ts b/backend/src/plugins/Spam/util/getRecentActionCount.ts index c7ae5c55..5b0b0aef 100644 --- a/backend/src/plugins/Spam/util/getRecentActionCount.ts +++ b/backend/src/plugins/Spam/util/getRecentActionCount.ts @@ -1,5 +1,5 @@ -import { RecentActionType, SpamPluginType } from "../types"; import { GuildPluginData } from "knub"; +import { RecentActionType, SpamPluginType } from "../types"; export function getRecentActionCount( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Spam/util/getRecentActions.ts b/backend/src/plugins/Spam/util/getRecentActions.ts index 765a1300..ff3f31ac 100644 --- a/backend/src/plugins/Spam/util/getRecentActions.ts +++ b/backend/src/plugins/Spam/util/getRecentActions.ts @@ -1,5 +1,5 @@ -import { RecentActionType, SpamPluginType } from "../types"; import { GuildPluginData } from "knub"; +import { RecentActionType, SpamPluginType } from "../types"; export function getRecentActions( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Spam/util/logAndDetectMessageSpam.ts b/backend/src/plugins/Spam/util/logAndDetectMessageSpam.ts index d5cc946e..e34b34c2 100644 --- a/backend/src/plugins/Spam/util/logAndDetectMessageSpam.ts +++ b/backend/src/plugins/Spam/util/logAndDetectMessageSpam.ts @@ -1,28 +1,26 @@ -import { SavedMessage } from "../../../data/entities/SavedMessage"; -import { RecentActionType, SpamPluginType, TBaseSingleSpamConfig } from "../types"; -import moment from "moment-timezone"; -import { MuteResult } from "../../../plugins/Mutes/types"; -import { - convertDelayStringToMS, - DBDateFormat, - noop, - resolveMember, - stripObjectToScalars, - trimLines, -} from "../../../utils"; -import { LogType } from "../../../data/LogType"; -import { CaseTypes } from "../../../data/CaseTypes"; -import { logger } from "../../../logger"; +import { Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { MutesPlugin } from "../../../plugins/Mutes/MutesPlugin"; +import moment from "moment-timezone"; +import { + channelToConfigAccessibleChannel, + memberToConfigAccessibleMember, +} from "../../../utils/configAccessibleObjects"; +import { CaseTypes } from "../../../data/CaseTypes"; +import { SavedMessage } from "../../../data/entities/SavedMessage"; +import { LogType } from "../../../data/LogType"; +import { logger } from "../../../logger"; import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; +import { MutesPlugin } from "../../../plugins/Mutes/MutesPlugin"; +import { MuteResult } from "../../../plugins/Mutes/types"; +import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; +import { convertDelayStringToMS, DBDateFormat, noop, resolveMember, trimLines } from "../../../utils"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { RecentActionType, SpamPluginType, TBaseSingleSpamConfig } from "../types"; import { addRecentAction } from "./addRecentAction"; +import { clearRecentUserActions } from "./clearRecentUserActions"; import { getRecentActionCount } from "./getRecentActionCount"; import { getRecentActions } from "./getRecentActions"; -import { clearRecentUserActions } from "./clearRecentUserActions"; import { saveSpamArchives } from "./saveSpamArchives"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; export async function logAndDetectMessageSpam( pluginData: GuildPluginData, @@ -88,7 +86,7 @@ export async function logAndDetectMessageSpam( "Automatic spam detection", { caseArgs: { - modId: pluginData.client.user.id, + modId: pluginData.client.user!.id, postInCaseLogOverride: false, }, }, @@ -122,7 +120,9 @@ export async function logAndDetectMessageSpam( // Then, if enabled, remove the spam messages if (spamConfig.clean !== false) { msgIds.forEach(id => pluginData.state.logs.ignoreLog(LogType.MESSAGE_DELETE, id)); - pluginData.client.deleteMessages(savedMessage.channel_id, msgIds).catch(noop); + (pluginData.guild.channels.cache.get(savedMessage.channel_id as Snowflake)! as TextChannel | undefined) + ?.bulkDelete(msgIds as Snowflake[]) + .catch(noop); } // Store the ID of the last handled message @@ -145,7 +145,7 @@ export async function logAndDetectMessageSpam( clearRecentUserActions(pluginData, type, savedMessage.user_id, savedMessage.channel_id); // Generate a log from the detected messages - const channel = pluginData.guild.channels.get(savedMessage.channel_id); + const channel = pluginData.guild.channels.cache.get(savedMessage.channel_id as Snowflake); const archiveUrl = await saveSpamArchives(pluginData, uniqueMessages); // Create a case @@ -173,7 +173,7 @@ export async function logAndDetectMessageSpam( casesPlugin.createCase({ userId: savedMessage.user_id, - modId: pluginData.client.user.id, + modId: pluginData.client.user!.id, type: CaseTypes.Note, reason: caseText, automatic: true, @@ -182,8 +182,8 @@ export async function logAndDetectMessageSpam( // Create a log entry logs.log(LogType.MESSAGE_SPAM_DETECTED, { - member: stripObjectToScalars(member, ["user", "roles"]), - channel: stripObjectToScalars(channel), + member: memberToConfigAccessibleMember(member!), + channel: channelToConfigAccessibleChannel(channel!), description, limit: spamConfig.count, interval: spamConfig.interval, diff --git a/backend/src/plugins/Spam/util/logAndDetectOtherSpam.ts b/backend/src/plugins/Spam/util/logAndDetectOtherSpam.ts index b2fcb236..b9d5fd6d 100644 --- a/backend/src/plugins/Spam/util/logAndDetectOtherSpam.ts +++ b/backend/src/plugins/Spam/util/logAndDetectOtherSpam.ts @@ -1,15 +1,16 @@ import { GuildPluginData } from "knub"; -import { SpamPluginType, RecentActionType } from "../types"; -import { addRecentAction } from "./addRecentAction"; -import { getRecentActionCount } from "./getRecentActionCount"; -import { resolveMember, convertDelayStringToMS, stripObjectToScalars } from "../../../utils"; -import { MutesPlugin } from "../../../plugins/Mutes/MutesPlugin"; -import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; +import { memberToConfigAccessibleMember } from "../../../utils/configAccessibleObjects"; import { CaseTypes } from "../../../data/CaseTypes"; -import { clearRecentUserActions } from "./clearRecentUserActions"; import { LogType } from "../../../data/LogType"; +import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; +import { MutesPlugin } from "../../../plugins/Mutes/MutesPlugin"; import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; +import { convertDelayStringToMS, resolveMember } from "../../../utils"; import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { RecentActionType, SpamPluginType } from "../types"; +import { addRecentAction } from "./addRecentAction"; +import { clearRecentUserActions } from "./clearRecentUserActions"; +import { getRecentActionCount } from "./getRecentActionCount"; export async function logAndDetectOtherSpam( pluginData: GuildPluginData, @@ -47,7 +48,7 @@ export async function logAndDetectOtherSpam( "Automatic spam detection", { caseArgs: { - modId: pluginData.client.user.id, + modId: pluginData.client.user!.id, extraNotes: [`Details: ${details}`], }, }, @@ -68,7 +69,7 @@ export async function logAndDetectOtherSpam( const casesPlugin = pluginData.getPlugin(CasesPlugin); await casesPlugin.createCase({ userId, - modId: pluginData.client.user.id, + modId: pluginData.client.user!.id, type: CaseTypes.Note, reason: `Automatic spam detection: ${details}`, }); @@ -78,7 +79,7 @@ export async function logAndDetectOtherSpam( clearRecentUserActions(pluginData, RecentActionType.VoiceChannelMove, userId, actionGroupId); logs.log(LogType.OTHER_SPAM_DETECTED, { - member: stripObjectToScalars(member, ["user", "roles"]), + member: memberToConfigAccessibleMember(member!), description, limit: spamConfig.count, interval: spamConfig.interval, diff --git a/backend/src/plugins/Spam/util/logCensor.ts b/backend/src/plugins/Spam/util/logCensor.ts index aa0fb161..1b09c16e 100644 --- a/backend/src/plugins/Spam/util/logCensor.ts +++ b/backend/src/plugins/Spam/util/logCensor.ts @@ -1,10 +1,11 @@ +import { Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; -import { SpamPluginType, RecentActionType } from "../types"; import { SavedMessage } from "../../../data/entities/SavedMessage"; +import { RecentActionType, SpamPluginType } from "../types"; import { logAndDetectMessageSpam } from "./logAndDetectMessageSpam"; export async function logCensor(pluginData: GuildPluginData, savedMessage: SavedMessage) { - const member = pluginData.guild.members.get(savedMessage.user_id); + const member = pluginData.guild.members.cache.get(savedMessage.user_id as Snowflake); const config = await pluginData.config.getMatchingConfig({ userId: savedMessage.user_id, channelId: savedMessage.channel_id, diff --git a/backend/src/plugins/Spam/util/onMessageCreate.ts b/backend/src/plugins/Spam/util/onMessageCreate.ts index 71e1bb3f..769fe50d 100644 --- a/backend/src/plugins/Spam/util/onMessageCreate.ts +++ b/backend/src/plugins/Spam/util/onMessageCreate.ts @@ -1,13 +1,14 @@ +import { Snowflake } from "discord.js"; import { GuildPluginData } from "knub"; -import { SpamPluginType, RecentActionType } from "../types"; import { SavedMessage } from "../../../data/entities/SavedMessage"; -import { getUserMentions, getRoleMentions, getUrlsInString, getEmojiInString } from "../../../utils"; +import { getEmojiInString, getRoleMentions, getUrlsInString, getUserMentions } from "../../../utils"; +import { RecentActionType, SpamPluginType } from "../types"; import { logAndDetectMessageSpam } from "./logAndDetectMessageSpam"; export async function onMessageCreate(pluginData: GuildPluginData, savedMessage: SavedMessage) { if (savedMessage.is_bot) return; - const member = pluginData.guild.members.get(savedMessage.user_id); + const member = pluginData.guild.members.cache.get(savedMessage.user_id as Snowflake); const config = await pluginData.config.getMatchingConfig({ userId: savedMessage.user_id, channelId: savedMessage.channel_id, diff --git a/backend/src/plugins/Spam/util/saveSpamArchives.ts b/backend/src/plugins/Spam/util/saveSpamArchives.ts index 9a3b3cac..d6d062a1 100644 --- a/backend/src/plugins/Spam/util/saveSpamArchives.ts +++ b/backend/src/plugins/Spam/util/saveSpamArchives.ts @@ -1,7 +1,7 @@ -import { SavedMessage } from "../../../data/entities/SavedMessage"; -import moment from "moment-timezone"; -import { getBaseUrl } from "../../../pluginUtils"; import { GuildPluginData } from "knub"; +import moment from "moment-timezone"; +import { SavedMessage } from "../../../data/entities/SavedMessage"; +import { getBaseUrl } from "../../../pluginUtils"; import { SpamPluginType } from "../types"; const SPAM_ARCHIVE_EXPIRY_DAYS = 90; diff --git a/backend/src/plugins/Starboard/StarboardPlugin.ts b/backend/src/plugins/Starboard/StarboardPlugin.ts index 817828a1..a7ab3ff2 100644 --- a/backend/src/plugins/Starboard/StarboardPlugin.ts +++ b/backend/src/plugins/Starboard/StarboardPlugin.ts @@ -1,14 +1,14 @@ import { PluginOptions } from "knub"; -import { ConfigSchema, defaultStarboardOpts, StarboardPluginType } from "./types"; -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { trimPluginDescription } from "../../utils"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildStarboardMessages } from "../../data/GuildStarboardMessages"; import { GuildStarboardReactions } from "../../data/GuildStarboardReactions"; -import { onMessageDelete } from "./util/onMessageDelete"; +import { trimPluginDescription } from "../../utils"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { MigratePinsCmd } from "./commands/MigratePinsCmd"; import { StarboardReactionAddEvt } from "./events/StarboardReactionAddEvt"; -import { StarboardReactionRemoveEvt, StarboardReactionRemoveAllEvt } from "./events/StarboardReactionRemoveEvts"; +import { StarboardReactionRemoveAllEvt, StarboardReactionRemoveEvt } from "./events/StarboardReactionRemoveEvts"; +import { ConfigSchema, defaultStarboardOpts, StarboardPluginType } from "./types"; +import { onMessageDelete } from "./util/onMessageDelete"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/Starboard/commands/MigratePinsCmd.ts b/backend/src/plugins/Starboard/commands/MigratePinsCmd.ts index f109150a..7fba0241 100644 --- a/backend/src/plugins/Starboard/commands/MigratePinsCmd.ts +++ b/backend/src/plugins/Starboard/commands/MigratePinsCmd.ts @@ -1,7 +1,7 @@ +import { Snowflake, TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { starboardCmd } from "../types"; -import { sendSuccessMessage, sendErrorMessage } from "../../../pluginUtils"; -import { TextChannel } from "eris"; import { saveMessageToStarboard } from "../util/saveMessageToStarboard"; export const MigratePinsCmd = starboardCmd({ @@ -23,15 +23,15 @@ export const MigratePinsCmd = starboardCmd({ return; } - const starboardChannel = pluginData.guild.channels.get(starboard.channel_id); + const starboardChannel = pluginData.guild.channels.cache.get(starboard.channel_id as Snowflake); if (!starboardChannel || !(starboardChannel instanceof TextChannel)) { sendErrorMessage(pluginData, msg.channel, "Starboard has an unknown/invalid channel id"); return; } - msg.channel.createMessage(`Migrating pins from <#${args.pinChannel.id}> to <#${starboardChannel.id}>...`); + msg.channel.send(`Migrating pins from <#${args.pinChannel.id}> to <#${starboardChannel.id}>...`); - const pins = await args.pinChannel.getPins(); + const pins = [...(await args.pinChannel.messages.fetchPinned().catch(() => [])).values()]; pins.reverse(); // Migrate pins starting from the oldest message for (const pin of pins) { diff --git a/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts b/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts index 4c123ac8..6727e5c7 100644 --- a/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts +++ b/backend/src/plugins/Starboard/events/StarboardReactionAddEvt.ts @@ -1,9 +1,9 @@ +import { Message, Snowflake, TextChannel } from "discord.js"; +import { noop, resolveMember } from "../../../utils"; +import { allStarboardsLock } from "../../../utils/lockNameHelpers"; import { starboardEvt } from "../types"; -import { Message, TextChannel } from "eris"; -import { UnknownUser, resolveMember, noop, resolveUser } from "../../../utils"; import { saveMessageToStarboard } from "../util/saveMessageToStarboard"; import { updateStarboardMessageStarCount } from "../util/updateStarboardMessageStarCount"; -import { allStarboardsLock } from "../../../utils/lockNameHelpers"; export const StarboardReactionAddEvt = starboardEvt({ event: "messageReactionAdd", @@ -11,30 +11,27 @@ export const StarboardReactionAddEvt = starboardEvt({ async listener(meta) { const pluginData = meta.pluginData; - let msg = meta.args.message as Message; - const userId = meta.args.member.id; - const emoji = meta.args.emoji; + let msg = meta.args.reaction.message as Message; + const userId = meta.args.user.id; + const emoji = meta.args.reaction.emoji; if (!msg.author) { // Message is not cached, fetch it try { - msg = await msg.channel.getMessage(msg.id); + msg = await msg.channel.messages.fetch(msg.id); } catch { // Sometimes we get this event for messages we can't fetch with getMessage; ignore silently return; } } - // No self-votes! - if (msg.author.id === userId) return; - const member = await resolveMember(pluginData.client, pluginData.guild, userId); - if (!member || member.bot) return; + if (!member || member.user.bot) return; const config = await pluginData.config.getMatchingConfig({ member, channelId: msg.channel.id, - categoryId: (msg.channel as TextChannel).parentID, + categoryId: (msg.channel as TextChannel).parentId, }); const boardLock = await pluginData.locks.acquire(allStarboardsLock()); @@ -61,7 +58,10 @@ export const StarboardReactionAddEvt = starboardEvt({ }); }); + const selfStar = msg.author.id === userId; for (const starboard of applicableStarboards) { + if (selfStar && !starboard.allow_selfstars) continue; + // Save reaction into the database await pluginData.state.starboardReactions.createStarboardReaction(msg.id, userId).catch(noop); @@ -76,9 +76,11 @@ export const StarboardReactionAddEvt = starboardEvt({ // If the message has already been posted to this starboard, update star counts if (starboard.show_star_count) { for (const starboardMessage of starboardMessages) { - const realStarboardMessage = await pluginData.client.getMessage( - starboardMessage.starboard_channel_id, - starboardMessage.starboard_message_id, + const channel = pluginData.guild.channels.cache.get( + starboardMessage.starboard_channel_id as Snowflake, + ) as TextChannel; + const realStarboardMessage = await channel.messages.fetch( + starboardMessage.starboard_message_id as Snowflake, ); await updateStarboardMessageStarCount( starboard, diff --git a/backend/src/plugins/Starboard/events/StarboardReactionRemoveEvts.ts b/backend/src/plugins/Starboard/events/StarboardReactionRemoveEvts.ts index bc4bfae4..203c4bde 100644 --- a/backend/src/plugins/Starboard/events/StarboardReactionRemoveEvts.ts +++ b/backend/src/plugins/Starboard/events/StarboardReactionRemoveEvts.ts @@ -6,7 +6,10 @@ export const StarboardReactionRemoveEvt = starboardEvt({ async listener(meta) { const boardLock = await meta.pluginData.locks.acquire(allStarboardsLock()); - await meta.pluginData.state.starboardReactions.deleteStarboardReaction(meta.args.message.id, meta.args.userID); + await meta.pluginData.state.starboardReactions.deleteStarboardReaction( + meta.args.reaction.message.id, + meta.args.user.id, + ); boardLock.unlock(); }, }); diff --git a/backend/src/plugins/Starboard/types.ts b/backend/src/plugins/Starboard/types.ts index a8d160b4..4e1f54eb 100644 --- a/backend/src/plugins/Starboard/types.ts +++ b/backend/src/plugins/Starboard/types.ts @@ -1,14 +1,15 @@ import * as t from "io-ts"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub"; -import { tNullable, tDeepPartial } from "../../utils"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildStarboardMessages } from "../../data/GuildStarboardMessages"; import { GuildStarboardReactions } from "../../data/GuildStarboardReactions"; +import { tDeepPartial, tNullable } from "../../utils"; const StarboardOpts = t.type({ channel_id: t.string, stars_required: t.number, star_emoji: tNullable(t.array(t.string)), + allow_selfstars: tNullable(t.boolean), copy_full_embed: tNullable(t.boolean), enabled: tNullable(t.boolean), show_star_count: t.boolean, diff --git a/backend/src/plugins/Starboard/util/createStarboardEmbedFromMessage.ts b/backend/src/plugins/Starboard/util/createStarboardEmbedFromMessage.ts index 9625556c..dc6e8781 100644 --- a/backend/src/plugins/Starboard/util/createStarboardEmbedFromMessage.ts +++ b/backend/src/plugins/Starboard/util/createStarboardEmbedFromMessage.ts @@ -1,6 +1,6 @@ -import { EmbedWith, EMPTY_CHAR, messageLink } from "../../../utils"; -import { EmbedOptions, GuildChannel, Message } from "eris"; +import { GuildChannel, Message } from "discord.js"; import path from "path"; +import { EmbedWith, EMPTY_CHAR } from "../../../utils"; const imageAttachmentExtensions = ["jpeg", "jpg", "png", "gif", "webp"]; const audioAttachmentExtensions = ["wav", "mp3", "m4a"]; @@ -18,19 +18,17 @@ export function createStarboardEmbedFromMessage( text: `#${(msg.channel as GuildChannel).name}`, }, author: { - name: `${msg.author.username}#${msg.author.discriminator}`, + name: msg.author.tag, }, fields: [], - timestamp: new Date(msg.timestamp).toISOString(), + timestamp: msg.createdTimestamp, }; if (color != null) { embed.color = color; } - if (msg.author.avatarURL) { - embed.author.icon_url = msg.author.avatarURL; - } + embed.author.icon_url = msg.author.displayAvatarURL({ dynamic: true }); // The second condition here checks for messages with only an image link that is then embedded. // The message content in that case is hidden by the Discord client, so we hide it here too. @@ -59,15 +57,15 @@ export function createStarboardEmbedFromMessage( } // If there are no embeds, add the first image attachment explicitly - else if (msg.attachments.length) { + else if (msg.attachments.size) { for (const attachment of msg.attachments) { const ext = path - .extname(attachment.filename) + .extname(attachment[1].name!) .slice(1) .toLowerCase(); if (imageAttachmentExtensions.includes(ext)) { - embed.image = { url: attachment.url }; + embed.image = { url: attachment[1].url }; break; } diff --git a/backend/src/plugins/Starboard/util/createStarboardPseudoFooterForMessage.ts b/backend/src/plugins/Starboard/util/createStarboardPseudoFooterForMessage.ts index 08172831..49d2cdde 100644 --- a/backend/src/plugins/Starboard/util/createStarboardPseudoFooterForMessage.ts +++ b/backend/src/plugins/Starboard/util/createStarboardPseudoFooterForMessage.ts @@ -1,4 +1,4 @@ -import { EmbedField, EmojiOptions, GuildChannel, Message } from "eris"; +import { EmbedField, Message } from "discord.js"; import { EMPTY_CHAR, messageLink } from "../../../utils"; import { TStarboardOpts } from "../types"; @@ -23,5 +23,6 @@ export function createStarboardPseudoFooterForMessage( return { name: EMPTY_CHAR, value: content, + inline: false, }; } diff --git a/backend/src/plugins/Starboard/util/onMessageDelete.ts b/backend/src/plugins/Starboard/util/onMessageDelete.ts index 74477afe..fb6c7f2e 100644 --- a/backend/src/plugins/Starboard/util/onMessageDelete.ts +++ b/backend/src/plugins/Starboard/util/onMessageDelete.ts @@ -1,5 +1,5 @@ -import { SavedMessage } from "../../../data/entities/SavedMessage"; import { GuildPluginData } from "knub"; +import { SavedMessage } from "../../../data/entities/SavedMessage"; import { StarboardPluginType } from "../types"; import { removeMessageFromStarboard } from "./removeMessageFromStarboard"; import { removeMessageFromStarboardMessages } from "./removeMessageFromStarboardMessages"; diff --git a/backend/src/plugins/Starboard/util/preprocessStaticConfig.ts b/backend/src/plugins/Starboard/util/preprocessStaticConfig.ts index b690fbf8..708b4a89 100644 --- a/backend/src/plugins/Starboard/util/preprocessStaticConfig.ts +++ b/backend/src/plugins/Starboard/util/preprocessStaticConfig.ts @@ -1,5 +1,5 @@ -import { PartialConfigSchema, defaultStarboardOpts } from "../types"; import * as t from "io-ts"; +import { defaultStarboardOpts, PartialConfigSchema } from "../types"; export function preprocessStaticConfig(config: t.TypeOf) { if (config.boards) { diff --git a/backend/src/plugins/Starboard/util/saveMessageToStarboard.ts b/backend/src/plugins/Starboard/util/saveMessageToStarboard.ts index e7587abd..ed2bd3f9 100644 --- a/backend/src/plugins/Starboard/util/saveMessageToStarboard.ts +++ b/backend/src/plugins/Starboard/util/saveMessageToStarboard.ts @@ -1,9 +1,6 @@ +import { Message, MessageEmbedOptions, Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; import { StarboardPluginType, TStarboardOpts } from "../types"; -import { Message, GuildChannel, TextChannel, Embed } from "eris"; -import moment from "moment-timezone"; -import { EmbedWith, EMPTY_CHAR, messageLink } from "../../../utils"; -import path from "path"; import { createStarboardEmbedFromMessage } from "./createStarboardEmbedFromMessage"; import { createStarboardPseudoFooterForMessage } from "./createStarboardPseudoFooterForMessage"; @@ -12,13 +9,13 @@ export async function saveMessageToStarboard( msg: Message, starboard: TStarboardOpts, ) { - const channel = pluginData.guild.channels.get(starboard.channel_id); + const channel = pluginData.guild.channels.cache.get(starboard.channel_id as Snowflake); if (!channel) return; const starCount = (await pluginData.state.starboardReactions.getAllReactionsForMessageId(msg.id)).length; const embed = createStarboardEmbedFromMessage(msg, Boolean(starboard.copy_full_embed), starboard.color); embed.fields!.push(createStarboardPseudoFooterForMessage(starboard, msg, starboard.star_emoji![0], starCount)); - const starboardMessage = await (channel as TextChannel).createMessage({ embed }); + const starboardMessage = await (channel as TextChannel).send({ embeds: [embed as MessageEmbedOptions] }); await pluginData.state.starboardMessages.createStarboardMessage(channel.id, msg.id, starboardMessage.id); } diff --git a/backend/src/plugins/Starboard/util/updateStarboardMessageStarCount.ts b/backend/src/plugins/Starboard/util/updateStarboardMessageStarCount.ts index f476444e..04f6002c 100644 --- a/backend/src/plugins/Starboard/util/updateStarboardMessageStarCount.ts +++ b/backend/src/plugins/Starboard/util/updateStarboardMessageStarCount.ts @@ -1,7 +1,6 @@ -import { Client, GuildTextableChannel, Message } from "eris"; -import { noop } from "../../../utils"; -import { createStarboardPseudoFooterForMessage } from "./createStarboardPseudoFooterForMessage"; +import { Message } from "discord.js"; import { TStarboardOpts } from "../types"; +import { createStarboardPseudoFooterForMessage } from "./createStarboardPseudoFooterForMessage"; import Timeout = NodeJS.Timeout; const DEBOUNCE_DELAY = 1000; @@ -24,6 +23,6 @@ export async function updateStarboardMessageStarCount( const embed = starboardMessage.embeds[0]!; embed.fields!.pop(); // Remove pseudo footer embed.fields!.push(createStarboardPseudoFooterForMessage(starboard, originalMessage, starEmoji, starCount)); // Create new pseudo footer - starboardMessage.edit({ embed }); + starboardMessage.edit({ embeds: [embed] }); }, DEBOUNCE_DELAY); } diff --git a/backend/src/plugins/Tags/TagsPlugin.ts b/backend/src/plugins/Tags/TagsPlugin.ts index 5a26af84..0ea369a0 100644 --- a/backend/src/plugins/Tags/TagsPlugin.ts +++ b/backend/src/plugins/Tags/TagsPlugin.ts @@ -1,25 +1,26 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { ConfigSchema, TagsPluginType } from "./types"; +import { Snowflake } from "discord.js"; +import humanizeDuration from "humanize-duration"; import { PluginOptions } from "knub"; +import moment from "moment-timezone"; +import { StrictValidationError } from "src/validatorUtils"; import { GuildArchives } from "../../data/GuildArchives"; -import { GuildTags } from "../../data/GuildTags"; -import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildLogs } from "../../data/GuildLogs"; -import { onMessageCreate } from "./util/onMessageCreate"; -import { onMessageDelete } from "./util/onMessageDelete"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { GuildTags } from "../../data/GuildTags"; +import { mapToPublicFn } from "../../pluginUtils"; +import { convertDelayStringToMS } from "../../utils"; +import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { TagCreateCmd } from "./commands/TagCreateCmd"; import { TagDeleteCmd } from "./commands/TagDeleteCmd"; import { TagEvalCmd } from "./commands/TagEvalCmd"; import { TagListCmd } from "./commands/TagListCmd"; import { TagSourceCmd } from "./commands/TagSourceCmd"; -import moment from "moment-timezone"; -import humanizeDuration from "humanize-duration"; -import { convertDelayStringToMS } from "../../utils"; -import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; -import { mapToPublicFn } from "../../pluginUtils"; -import { renderTagBody } from "./util/renderTagBody"; +import { ConfigSchema, TagsPluginType } from "./types"; import { findTagByName } from "./util/findTagByName"; -import { StrictValidationError } from "src/validatorUtils"; +import { onMessageCreate } from "./util/onMessageCreate"; +import { onMessageDelete } from "./util/onMessageDelete"; +import { renderTagBody } from "./util/renderTagBody"; const defaultOptions: PluginOptions = { config: { @@ -149,6 +150,7 @@ export const TagsPlugin = zeppelinGuildPlugin()({ }, timeAdd(...args) { + if (args.length === 0) return; let reference; let delay; @@ -170,6 +172,7 @@ export const TagsPlugin = zeppelinGuildPlugin()({ }, timeSub(...args) { + if (args.length === 0) return; let reference; let delay; @@ -214,11 +217,14 @@ export const TagsPlugin = zeppelinGuildPlugin()({ return input; } - if (pluginData.guild.members.has(input) || pluginData.client.users.has(input)) { + if ( + pluginData.guild.members.cache.has(input as Snowflake) || + pluginData.client.users.resolve(input as Snowflake) + ) { return `<@!${input}>`; } - if (pluginData.guild.channels.has(input) || pluginData.client.channelGuildMap[input]) { + if (pluginData.guild.channels.cache.has(input as Snowflake)) { return `<#${input}>`; } diff --git a/backend/src/plugins/Tags/commands/TagCreateCmd.ts b/backend/src/plugins/Tags/commands/TagCreateCmd.ts index c55a5cab..0595e4bb 100644 --- a/backend/src/plugins/Tags/commands/TagCreateCmd.ts +++ b/backend/src/plugins/Tags/commands/TagCreateCmd.ts @@ -1,7 +1,7 @@ -import { tagsCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { parseTemplate, TemplateParseError } from "../../../templateFormatter"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { parseTemplate, TemplateParseError } from "../../../templateFormatter"; +import { tagsCmd } from "../types"; export const TagCreateCmd = tagsCmd({ trigger: "tag", diff --git a/backend/src/plugins/Tags/commands/TagDeleteCmd.ts b/backend/src/plugins/Tags/commands/TagDeleteCmd.ts index 40875d21..47cb623d 100644 --- a/backend/src/plugins/Tags/commands/TagDeleteCmd.ts +++ b/backend/src/plugins/Tags/commands/TagDeleteCmd.ts @@ -1,6 +1,6 @@ -import { tagsCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { tagsCmd } from "../types"; export const TagDeleteCmd = tagsCmd({ trigger: "tag delete", diff --git a/backend/src/plugins/Tags/commands/TagEvalCmd.ts b/backend/src/plugins/Tags/commands/TagEvalCmd.ts index 61dbea9e..9ce1bf00 100644 --- a/backend/src/plugins/Tags/commands/TagEvalCmd.ts +++ b/backend/src/plugins/Tags/commands/TagEvalCmd.ts @@ -1,10 +1,9 @@ -import { tagsCmd } from "../types"; +import { memberToConfigAccessibleMember, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { MessageContent } from "eris"; -import { TemplateParseError } from "../../../templateFormatter"; import { sendErrorMessage } from "../../../pluginUtils"; +import { TemplateParseError } from "../../../templateFormatter"; +import { tagsCmd } from "../types"; import { renderTagBody } from "../util/renderTagBody"; -import { stripObjectToScalars } from "../../../utils"; export const TagEvalCmd = tagsCmd({ trigger: "tag eval", @@ -21,8 +20,8 @@ export const TagEvalCmd = tagsCmd({ args.body, [], { - member: stripObjectToScalars(msg.member, ["user"]), - user: stripObjectToScalars(msg.member.user), + member: memberToConfigAccessibleMember(msg.member), + user: userToConfigAccessibleUser(msg.member.user), }, { member: msg.member }, ); @@ -32,7 +31,7 @@ export const TagEvalCmd = tagsCmd({ return; } - msg.channel.createMessage(rendered); + msg.channel.send(rendered); } catch (e) { if (e instanceof TemplateParseError) { sendErrorMessage(pluginData, msg.channel, `Failed to render tag: ${e.message}`); diff --git a/backend/src/plugins/Tags/commands/TagListCmd.ts b/backend/src/plugins/Tags/commands/TagListCmd.ts index 9e10892e..41febd5d 100644 --- a/backend/src/plugins/Tags/commands/TagListCmd.ts +++ b/backend/src/plugins/Tags/commands/TagListCmd.ts @@ -1,5 +1,5 @@ -import { tagsCmd } from "../types"; import { createChunkedMessage } from "../../../utils"; +import { tagsCmd } from "../types"; export const TagListCmd = tagsCmd({ trigger: ["tag list", "tags", "taglist"], @@ -8,7 +8,7 @@ export const TagListCmd = tagsCmd({ async run({ message: msg, pluginData }) { const tags = await pluginData.state.tags.all(); if (tags.length === 0) { - msg.channel.createMessage(`No tags created yet! Use \`tag create\` command to create one.`); + msg.channel.send(`No tags created yet! Use \`tag create\` command to create one.`); return; } diff --git a/backend/src/plugins/Tags/commands/TagSourceCmd.ts b/backend/src/plugins/Tags/commands/TagSourceCmd.ts index ed3fde87..9e0e5cef 100644 --- a/backend/src/plugins/Tags/commands/TagSourceCmd.ts +++ b/backend/src/plugins/Tags/commands/TagSourceCmd.ts @@ -1,7 +1,7 @@ -import { tagsCmd } from "../types"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage, getBaseUrl, sendSuccessMessage } from "../../../pluginUtils"; import moment from "moment-timezone"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { getBaseUrl, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { tagsCmd } from "../types"; export const TagSourceCmd = tagsCmd({ trigger: "tag", @@ -35,6 +35,6 @@ export const TagSourceCmd = tagsCmd({ const archiveId = await pluginData.state.archives.create(tag.body, moment.utc().add(10, "minutes")); const url = pluginData.state.archives.getUrl(getBaseUrl(pluginData), archiveId); - msg.channel.createMessage(`Tag source:\n${url}`); + msg.channel.send(`Tag source:\n${url}`); }, }); diff --git a/backend/src/plugins/Tags/types.ts b/backend/src/plugins/Tags/types.ts index 348e2982..17ad0fc8 100644 --- a/backend/src/plugins/Tags/types.ts +++ b/backend/src/plugins/Tags/types.ts @@ -1,10 +1,10 @@ import * as t from "io-ts"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub"; -import { tNullable, tEmbed } from "../../utils"; import { GuildArchives } from "../../data/GuildArchives"; -import { GuildTags } from "../../data/GuildTags"; -import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildLogs } from "../../data/GuildLogs"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { GuildTags } from "../../data/GuildTags"; +import { tEmbed, tNullable } from "../../utils"; export const Tag = t.union([t.string, tEmbed]); diff --git a/backend/src/plugins/Tags/util/findTagByName.ts b/backend/src/plugins/Tags/util/findTagByName.ts index c8e3807a..4ec61ec5 100644 --- a/backend/src/plugins/Tags/util/findTagByName.ts +++ b/backend/src/plugins/Tags/util/findTagByName.ts @@ -1,7 +1,7 @@ -import { GuildPluginData } from "knub"; -import { Tag, TagsPluginType } from "../types"; -import { ExtendedMatchParams } from "knub/dist/config/PluginConfigManager"; import * as t from "io-ts"; +import { GuildPluginData } from "knub"; +import { ExtendedMatchParams } from "knub/dist/config/PluginConfigManager"; +import { Tag, TagsPluginType } from "../types"; export async function findTagByName( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Tags/util/matchAndRenderTagFromString.ts b/backend/src/plugins/Tags/util/matchAndRenderTagFromString.ts index bddc41cc..db436067 100644 --- a/backend/src/plugins/Tags/util/matchAndRenderTagFromString.ts +++ b/backend/src/plugins/Tags/util/matchAndRenderTagFromString.ts @@ -1,10 +1,10 @@ -import { ExtendedMatchParams } from "knub/dist/config/PluginConfigManager"; +import { GuildMember } from "discord.js"; +import escapeStringRegexp from "escape-string-regexp"; import { GuildPluginData } from "knub"; +import { ExtendedMatchParams } from "knub/dist/config/PluginConfigManager"; +import { StrictMessageContent } from "../../../utils"; import { TagsPluginType, TTagCategory } from "../types"; import { renderTagFromString } from "./renderTagFromString"; -import { convertDelayStringToMS, StrictMessageContent } from "../../../utils"; -import escapeStringRegexp from "escape-string-regexp"; -import { Member } from "eris"; interface BaseResult { renderedContent: StrictMessageContent; @@ -26,7 +26,7 @@ type Result = ResultWithCategory | ResultWithoutCategory; export async function matchAndRenderTagFromString( pluginData: GuildPluginData, str: string, - member: Member, + member: GuildMember, extraMatchParams: ExtendedMatchParams = {}, ): Promise { const config = await pluginData.config.getMatchingConfig({ diff --git a/backend/src/plugins/Tags/util/onMessageCreate.ts b/backend/src/plugins/Tags/util/onMessageCreate.ts index 4a58e5a6..43448df5 100644 --- a/backend/src/plugins/Tags/util/onMessageCreate.ts +++ b/backend/src/plugins/Tags/util/onMessageCreate.ts @@ -1,12 +1,13 @@ -import { TagsPluginType } from "../types"; -import { SavedMessage } from "../../../data/entities/SavedMessage"; +import { Snowflake, TextChannel } from "discord.js"; import { GuildPluginData } from "knub"; -import { convertDelayStringToMS, noop, resolveMember, tStrictMessageContent } from "../../../utils"; -import { validate } from "../../../validatorUtils"; +import { erisAllowedMentionsToDjsMentionOptions } from "src/utils/erisAllowedMentionsToDjsMentionOptions"; +import { SavedMessage } from "../../../data/entities/SavedMessage"; import { LogType } from "../../../data/LogType"; -import { TextChannel } from "eris"; -import { matchAndRenderTagFromString } from "./matchAndRenderTagFromString"; +import { convertDelayStringToMS, resolveMember, tStrictMessageContent } from "../../../utils"; import { messageIsEmpty } from "../../../utils/messageIsEmpty"; +import { validate } from "../../../validatorUtils"; +import { TagsPluginType } from "../types"; +import { matchAndRenderTagFromString } from "./matchAndRenderTagFromString"; export async function onMessageCreate(pluginData: GuildPluginData, msg: SavedMessage) { if (msg.is_bot) return; @@ -15,18 +16,18 @@ export async function onMessageCreate(pluginData: GuildPluginData, msg: SavedMessage) { // Command message was deleted -> delete the response as well const commandMsgResponse = await pluginData.state.tags.findResponseByCommandMessageId(msg.id); if (commandMsgResponse) { - const channel = pluginData.guild.channels.get(msg.channel_id) as TextChannel; + const channel = pluginData.guild.channels.cache.get(msg.channel_id as Snowflake) as TextChannel; if (!channel) return; const responseMsg = await pluginData.state.savedMessages.find(commandMsgResponse.response_message_id); if (!responseMsg || responseMsg.deleted_at != null) return; - await channel.deleteMessage(commandMsgResponse.response_message_id); + await channel.messages.delete(commandMsgResponse.response_message_id as Snowflake); return; } // Response was deleted -> delete the command message as well const responseMsgResponse = await pluginData.state.tags.findResponseByResponseMessageId(msg.id); if (responseMsgResponse) { - const channel = pluginData.guild.channels.get(msg.channel_id) as TextChannel; + const channel = pluginData.guild.channels.cache.get(msg.channel_id as Snowflake) as TextChannel; if (!channel) return; const commandMsg = await pluginData.state.savedMessages.find(responseMsgResponse.command_message_id); if (!commandMsg || commandMsg.deleted_at != null) return; - await channel.deleteMessage(responseMsgResponse.command_message_id); + await channel.messages.delete(responseMsgResponse.command_message_id as Snowflake); return; } } diff --git a/backend/src/plugins/Tags/util/renderTagBody.ts b/backend/src/plugins/Tags/util/renderTagBody.ts index 735455dc..725275f1 100644 --- a/backend/src/plugins/Tags/util/renderTagBody.ts +++ b/backend/src/plugins/Tags/util/renderTagBody.ts @@ -1,10 +1,10 @@ -import { renderTemplate } from "../../../templateFormatter"; -import { GuildPluginData } from "knub"; -import { Tag, TagsPluginType } from "../types"; -import { renderRecursively, StrictMessageContent } from "../../../utils"; import * as t from "io-ts"; -import { findTagByName } from "./findTagByName"; +import { GuildPluginData } from "knub"; import { ExtendedMatchParams } from "knub/dist/config/PluginConfigManager"; +import { renderTemplate } from "../../../templateFormatter"; +import { renderRecursively, StrictMessageContent } from "../../../utils"; +import { Tag, TagsPluginType } from "../types"; +import { findTagByName } from "./findTagByName"; export async function renderTagBody( pluginData: GuildPluginData, diff --git a/backend/src/plugins/Tags/util/renderTagFromString.ts b/backend/src/plugins/Tags/util/renderTagFromString.ts index 5c9986ac..d54d836e 100644 --- a/backend/src/plugins/Tags/util/renderTagFromString.ts +++ b/backend/src/plugins/Tags/util/renderTagFromString.ts @@ -1,13 +1,13 @@ -import { Tag, TagsPluginType } from "../types"; -import { Member } from "eris"; +import { GuildMember } from "discord.js"; import * as t from "io-ts"; -import { renderRecursively, StrictMessageContent, stripObjectToScalars } from "../../../utils"; -import { parseArguments } from "knub-command-manager"; -import { TemplateParseError } from "../../../templateFormatter"; import { GuildPluginData } from "knub"; -import { logger } from "../../../logger"; -import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { parseArguments } from "knub-command-manager"; +import { memberToConfigAccessibleMember, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; import { LogType } from "../../../data/LogType"; +import { TemplateParseError } from "../../../templateFormatter"; +import { StrictMessageContent } from "../../../utils"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { Tag, TagsPluginType } from "../types"; import { renderTagBody } from "./renderTagBody"; export async function renderTagFromString( @@ -16,7 +16,7 @@ export async function renderTagFromString( prefix: string, tagName: string, tagBody: t.TypeOf, - member: Member, + member: GuildMember, ): Promise { const variableStr = str.slice(prefix.length + tagName.length).trim(); const tagArgs = parseArguments(variableStr).map(v => v.value); @@ -28,8 +28,8 @@ export async function renderTagFromString( tagBody, tagArgs, { - member: stripObjectToScalars(member, ["user"]), - user: stripObjectToScalars(member.user), + member: memberToConfigAccessibleMember(member), + user: userToConfigAccessibleUser(member.user), }, { member }, ); diff --git a/backend/src/plugins/TimeAndDate/TimeAndDatePlugin.ts b/backend/src/plugins/TimeAndDate/TimeAndDatePlugin.ts index 7d079265..2f0d6cdf 100644 --- a/backend/src/plugins/TimeAndDate/TimeAndDatePlugin.ts +++ b/backend/src/plugins/TimeAndDate/TimeAndDatePlugin.ts @@ -1,19 +1,18 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { ConfigSchema, TimeAndDatePluginType } from "./types"; -import { GuildMemberTimezones } from "../../data/GuildMemberTimezones"; import { PluginOptions } from "knub"; +import { GuildMemberTimezones } from "../../data/GuildMemberTimezones"; +import { mapToPublicFn } from "../../pluginUtils"; +import { trimPluginDescription } from "../../utils"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import { ResetTimezoneCmd } from "./commands/ResetTimezoneCmd"; import { SetTimezoneCmd } from "./commands/SetTimezoneCmd"; import { ViewTimezoneCmd } from "./commands/ViewTimezoneCmd"; import { defaultDateFormats } from "./defaultDateFormats"; -import { Tail } from "../../utils/typeUtils"; -import { inGuildTz } from "./functions/inGuildTz"; -import { mapToPublicFn } from "../../pluginUtils"; +import { getDateFormat } from "./functions/getDateFormat"; import { getGuildTz } from "./functions/getGuildTz"; import { getMemberTz } from "./functions/getMemberTz"; -import { getDateFormat } from "./functions/getDateFormat"; +import { inGuildTz } from "./functions/inGuildTz"; import { inMemberTz } from "./functions/inMemberTz"; -import { ResetTimezoneCmd } from "./commands/ResetTimezoneCmd"; -import { trimPluginDescription } from "../../utils"; +import { ConfigSchema, TimeAndDatePluginType } from "./types"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts b/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts index 56cd21b7..db59965a 100644 --- a/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts +++ b/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts @@ -1,7 +1,6 @@ -import { timeAndDateCmd } from "../types"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendSuccessMessage } from "../../../pluginUtils"; import { getGuildTz } from "../functions/getGuildTz"; +import { timeAndDateCmd } from "../types"; export const ResetTimezoneCmd = timeAndDateCmd({ trigger: "timezone reset", diff --git a/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts b/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts index 0d69f47c..d88a0740 100644 --- a/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts +++ b/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts @@ -1,9 +1,9 @@ -import { timeAndDateCmd } from "../types"; +import { Util } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { isValidTimezone } from "../../../utils/isValidTimezone"; -import { disableInlineCode, trimLines } from "../../../utils"; +import { trimLines } from "../../../utils"; import { parseFuzzyTimezone } from "../../../utils/parseFuzzyTimezone"; +import { timeAndDateCmd } from "../types"; export const SetTimezoneCmd = timeAndDateCmd({ trigger: "timezone", @@ -20,7 +20,7 @@ export const SetTimezoneCmd = timeAndDateCmd({ pluginData, message.channel, trimLines(` - Invalid timezone: \`${disableInlineCode(args.timezone)}\` + Invalid timezone: \`${Util.escapeInlineCode(args.timezone)}\` Zeppelin uses timezone locations rather than specific timezone names. See the **TZ database name** column at for a list of valid options. `), diff --git a/backend/src/plugins/TimeAndDate/commands/ViewTimezoneCmd.ts b/backend/src/plugins/TimeAndDate/commands/ViewTimezoneCmd.ts index 292f46de..0c72bc4e 100644 --- a/backend/src/plugins/TimeAndDate/commands/ViewTimezoneCmd.ts +++ b/backend/src/plugins/TimeAndDate/commands/ViewTimezoneCmd.ts @@ -1,8 +1,5 @@ -import { timeAndDateCmd } from "../types"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendSuccessMessage } from "../../../pluginUtils"; -import { getMemberTz } from "../functions/getMemberTz"; import { getGuildTz } from "../functions/getGuildTz"; +import { timeAndDateCmd } from "../types"; export const ViewTimezoneCmd = timeAndDateCmd({ trigger: "timezone", @@ -13,11 +10,11 @@ export const ViewTimezoneCmd = timeAndDateCmd({ async run({ pluginData, message, args }) { const memberTimezone = await pluginData.state.memberTimezones.get(message.author.id); if (memberTimezone) { - message.channel.createMessage(`Your timezone is currently set to **${memberTimezone.timezone}**`); + message.channel.send(`Your timezone is currently set to **${memberTimezone.timezone}**`); return; } const serverTimezone = getGuildTz(pluginData); - message.channel.createMessage(`Your timezone is currently set to **${serverTimezone}** (server default)`); + message.channel.send(`Your timezone is currently set to **${serverTimezone}** (server default)`); }, }); diff --git a/backend/src/plugins/TimeAndDate/functions/getGuildTz.ts b/backend/src/plugins/TimeAndDate/functions/getGuildTz.ts index ed30d67e..922c7c88 100644 --- a/backend/src/plugins/TimeAndDate/functions/getGuildTz.ts +++ b/backend/src/plugins/TimeAndDate/functions/getGuildTz.ts @@ -1,5 +1,4 @@ import { GuildPluginData } from "knub"; -import { ZeppelinGuildConfig } from "../../../types"; import { TimeAndDatePluginType } from "../types"; export function getGuildTz(pluginData: GuildPluginData) { diff --git a/backend/src/plugins/TimeAndDate/functions/inGuildTz.ts b/backend/src/plugins/TimeAndDate/functions/inGuildTz.ts index 118e5789..d19a6d68 100644 --- a/backend/src/plugins/TimeAndDate/functions/inGuildTz.ts +++ b/backend/src/plugins/TimeAndDate/functions/inGuildTz.ts @@ -1,6 +1,6 @@ import { GuildPluginData } from "knub"; -import { TimeAndDatePluginType } from "../types"; import moment from "moment-timezone"; +import { TimeAndDatePluginType } from "../types"; import { getGuildTz } from "./getGuildTz"; export function inGuildTz(pluginData: GuildPluginData, input?: moment.Moment | number) { diff --git a/backend/src/plugins/TimeAndDate/functions/inMemberTz.ts b/backend/src/plugins/TimeAndDate/functions/inMemberTz.ts index f6adc85a..37ec7ca3 100644 --- a/backend/src/plugins/TimeAndDate/functions/inMemberTz.ts +++ b/backend/src/plugins/TimeAndDate/functions/inMemberTz.ts @@ -1,7 +1,6 @@ import { GuildPluginData } from "knub"; -import { TimeAndDatePluginType } from "../types"; import moment from "moment-timezone"; -import { getGuildTz } from "./getGuildTz"; +import { TimeAndDatePluginType } from "../types"; import { getMemberTz } from "./getMemberTz"; export async function inMemberTz( diff --git a/backend/src/plugins/TimeAndDate/types.ts b/backend/src/plugins/TimeAndDate/types.ts index 48fa5d3a..c0144bfd 100644 --- a/backend/src/plugins/TimeAndDate/types.ts +++ b/backend/src/plugins/TimeAndDate/types.ts @@ -1,7 +1,7 @@ import * as t from "io-ts"; -import { tNullable, tPartialDictionary } from "../../utils"; import { BasePluginType, typedGuildCommand } from "knub"; import { GuildMemberTimezones } from "../../data/GuildMemberTimezones"; +import { tNullable, tPartialDictionary } from "../../utils"; import { tValidTimezone } from "../../utils/tValidTimezone"; import { defaultDateFormats } from "./defaultDateFormats"; diff --git a/backend/src/plugins/UsernameSaver/UsernameSaverPlugin.ts b/backend/src/plugins/UsernameSaver/UsernameSaverPlugin.ts index fbbe1e81..3014350c 100644 --- a/backend/src/plugins/UsernameSaver/UsernameSaverPlugin.ts +++ b/backend/src/plugins/UsernameSaver/UsernameSaverPlugin.ts @@ -1,9 +1,9 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import * as t from "io-ts"; import { UsernameHistory } from "../../data/UsernameHistory"; import { Queue } from "../../Queue"; -import { UsernameSaverPluginType } from "./types"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { MessageCreateUpdateUsernameEvt, VoiceChannelJoinUpdateUsernameEvt } from "./events/UpdateUsernameEvts"; -import * as t from "io-ts"; +import { UsernameSaverPluginType } from "./types"; export const UsernameSaverPlugin = zeppelinGuildPlugin()({ name: "username_saver", diff --git a/backend/src/plugins/UsernameSaver/events/UpdateUsernameEvts.ts b/backend/src/plugins/UsernameSaver/events/UpdateUsernameEvts.ts index 8b8a8fb8..093dbce7 100644 --- a/backend/src/plugins/UsernameSaver/events/UpdateUsernameEvts.ts +++ b/backend/src/plugins/UsernameSaver/events/UpdateUsernameEvts.ts @@ -11,10 +11,10 @@ export const MessageCreateUpdateUsernameEvt = usernameSaverEvt({ }); export const VoiceChannelJoinUpdateUsernameEvt = usernameSaverEvt({ - event: "voiceChannelJoin", + event: "voiceStateUpdate", async listener(meta) { - if (meta.args.member.bot) return; - meta.pluginData.state.updateQueue.add(() => updateUsername(meta.pluginData, meta.args.member.user)); + if (meta.args.newState.member?.user.bot) return; + meta.pluginData.state.updateQueue.add(() => updateUsername(meta.pluginData, meta.args.newState.member!.user)); }, }); diff --git a/backend/src/plugins/UsernameSaver/updateUsername.ts b/backend/src/plugins/UsernameSaver/updateUsername.ts index 5e0a3288..207e6540 100644 --- a/backend/src/plugins/UsernameSaver/updateUsername.ts +++ b/backend/src/plugins/UsernameSaver/updateUsername.ts @@ -1,10 +1,10 @@ -import { User } from "eris"; +import { User } from "discord.js"; import { GuildPluginData } from "knub"; import { UsernameSaverPluginType } from "./types"; export async function updateUsername(pluginData: GuildPluginData, user: User) { if (!user) return; - const newUsername = `${user.username}#${user.discriminator}`; + const newUsername = user.tag; const latestEntry = await pluginData.state.usernameHistory.getLastEntry(user.id); if (!latestEntry || newUsername !== latestEntry.username) { await pluginData.state.usernameHistory.addEntry(user.id, newUsername); diff --git a/backend/src/plugins/Utility/UtilityPlugin.ts b/backend/src/plugins/Utility/UtilityPlugin.ts index 2a22bc38..a2e9a6e7 100644 --- a/backend/src/plugins/Utility/UtilityPlugin.ts +++ b/backend/src/plugins/Utility/UtilityPlugin.ts @@ -1,43 +1,44 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { ConfigSchema, UtilityPluginType } from "./types"; -import { GuildLogs } from "../../data/GuildLogs"; -import { GuildCases } from "../../data/GuildCases"; -import { GuildSavedMessages } from "../../data/GuildSavedMessages"; -import { GuildArchives } from "../../data/GuildArchives"; -import { Supporters } from "../../data/Supporters"; -import { ServerInfoCmd } from "./commands/ServerInfoCmd"; -import { RolesCmd } from "./commands/RolesCmd"; -import { LevelCmd } from "./commands/LevelCmd"; -import { SearchCmd } from "./commands/SearchCmd"; -import { BanSearchCmd } from "./commands/BanSearchCmd"; -import { UserInfoCmd } from "./commands/UserInfoCmd"; -import { NicknameResetCmd } from "./commands/NicknameResetCmd"; -import { NicknameCmd } from "./commands/NicknameCmd"; -import { PingCmd } from "./commands/PingCmd"; -import { SourceCmd } from "./commands/SourceCmd"; -import { ContextCmd } from "./commands/ContextCmd"; -import { VcmoveAllCmd, VcmoveCmd } from "./commands/VcmoveCmd"; -import { HelpCmd } from "./commands/HelpCmd"; -import { AboutCmd } from "./commands/AboutCmd"; import { PluginOptions } from "knub"; -import { activeReloads } from "./guildReloads"; +import { GuildArchives } from "../../data/GuildArchives"; +import { GuildCases } from "../../data/GuildCases"; +import { GuildLogs } from "../../data/GuildLogs"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; +import { Supporters } from "../../data/Supporters"; import { sendSuccessMessage } from "../../pluginUtils"; -import { ReloadGuildCmd } from "./commands/ReloadGuildCmd"; -import { JumboCmd } from "./commands/JumboCmd"; -import { AvatarCmd } from "./commands/AvatarCmd"; -import { CleanCmd } from "./commands/CleanCmd"; -import { InviteInfoCmd } from "./commands/InviteInfoCmd"; -import { ChannelInfoCmd } from "./commands/ChannelInfoCmd"; -import { MessageInfoCmd } from "./commands/MessageInfoCmd"; -import { InfoCmd } from "./commands/InfoCmd"; -import { SnowflakeInfoCmd } from "./commands/SnowflakeInfoCmd"; import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners"; -import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; -import { VcdisconnectCmd } from "./commands/VcdisconnectCmd"; import { ModActionsPlugin } from "../ModActions/ModActionsPlugin"; -import { refreshMembersIfNeeded } from "./refreshMembers"; -import { RoleInfoCmd } from "./commands/RoleInfoCmd"; +import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; +import { AboutCmd } from "./commands/AboutCmd"; +import { AvatarCmd } from "./commands/AvatarCmd"; +import { BanSearchCmd } from "./commands/BanSearchCmd"; +import { ChannelInfoCmd } from "./commands/ChannelInfoCmd"; +import { CleanArgs, cleanCmd, CleanCmd } from "./commands/CleanCmd"; +import { ContextCmd } from "./commands/ContextCmd"; import { EmojiInfoCmd } from "./commands/EmojiInfoCmd"; +import { HelpCmd } from "./commands/HelpCmd"; +import { InfoCmd } from "./commands/InfoCmd"; +import { InviteInfoCmd } from "./commands/InviteInfoCmd"; +import { JumboCmd } from "./commands/JumboCmd"; +import { LevelCmd } from "./commands/LevelCmd"; +import { MessageInfoCmd } from "./commands/MessageInfoCmd"; +import { NicknameCmd } from "./commands/NicknameCmd"; +import { NicknameResetCmd } from "./commands/NicknameResetCmd"; +import { PingCmd } from "./commands/PingCmd"; +import { ReloadGuildCmd } from "./commands/ReloadGuildCmd"; +import { RoleInfoCmd } from "./commands/RoleInfoCmd"; +import { RolesCmd } from "./commands/RolesCmd"; +import { SearchCmd } from "./commands/SearchCmd"; +import { ServerInfoCmd } from "./commands/ServerInfoCmd"; +import { SnowflakeInfoCmd } from "./commands/SnowflakeInfoCmd"; +import { SourceCmd } from "./commands/SourceCmd"; +import { UserInfoCmd } from "./commands/UserInfoCmd"; +import { VcdisconnectCmd } from "./commands/VcdisconnectCmd"; +import { VcmoveAllCmd, VcmoveCmd } from "./commands/VcmoveCmd"; +import { AutoJoinThreadEvt, AutoJoinThreadSyncEvt } from "./events/AutoJoinThreadEvt"; +import { activeReloads } from "./guildReloads"; +import { refreshMembersIfNeeded } from "./refreshMembers"; +import { ConfigSchema, UtilityPluginType } from "./types"; const defaultOptions: PluginOptions = { config: { @@ -67,6 +68,7 @@ const defaultOptions: PluginOptions = { jumbo_size: 128, can_avatar: false, info_on_single_result: true, + autojoin_threads: true, }, overrides: [ { @@ -148,6 +150,20 @@ export const UtilityPlugin = zeppelinGuildPlugin()({ EmojiInfoCmd, ], + // prettier-ignore + events: [ + AutoJoinThreadEvt, + AutoJoinThreadSyncEvt, + ], + + public: { + clean(pluginData) { + return (args: CleanArgs, msg) => { + cleanCmd(pluginData, args, msg); + }; + }, + }, + beforeLoad(pluginData) { const { state, guild } = pluginData; diff --git a/backend/src/plugins/Utility/commands/AboutCmd.ts b/backend/src/plugins/Utility/commands/AboutCmd.ts index e3f968ae..0d6f8493 100644 --- a/backend/src/plugins/Utility/commands/AboutCmd.ts +++ b/backend/src/plugins/Utility/commands/AboutCmd.ts @@ -1,13 +1,12 @@ -import { utilityCmd } from "../types"; -import { EmbedWith, multiSorter, resolveMember, sorter } from "../../../utils"; -import { GuildChannel, MessageContent, Role } from "eris"; -import { getCurrentUptime } from "../../../uptime"; +import { GuildChannel, MessageOptions } from "discord.js"; import humanizeDuration from "humanize-duration"; import LCL from "last-commit-log"; -import path from "path"; import moment from "moment-timezone"; import { rootDir } from "../../../paths"; +import { getCurrentUptime } from "../../../uptime"; +import { multiSorter, resolveMember, sorter } from "../../../utils"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { utilityCmd } from "../types"; export const AboutCmd = utilityCmd({ trigger: "about", @@ -40,8 +39,6 @@ export const AboutCmd = utilityCmd({ version = "?"; } - const shard = pluginData.client.shards.get(pluginData.client.guildShardMap[pluginData.guild.id])!; - const lastReload = humanizeDuration(Date.now() - pluginData.state.lastReload, { largest: 2, round: true, @@ -52,7 +49,7 @@ export const AboutCmd = utilityCmd({ ["Last reload", `${lastReload} ago`], ["Last update", lastUpdate], ["Version", version], - ["API latency", `${shard.latency}ms`], + ["API latency", `${pluginData.client.ws.ping}ms`], ["Server timezone", timeAndDate.getGuildTz()], ]; @@ -64,24 +61,22 @@ export const AboutCmd = utilityCmd({ ); loadedPlugins.sort(); - const aboutContent: MessageContent & { embed: EmbedWith<"title" | "fields"> } = { - embed: { - title: `About ${pluginData.client.user.username}`, - fields: [ - { - name: "Status", - value: basicInfoRows - .map(([label, value]) => { - return `${label}: **${value}**`; - }) - .join("\n"), - }, - { - name: `Loaded plugins on this server (${loadedPlugins.length})`, - value: loadedPlugins.join(", "), - }, - ], - }, + const aboutContent: MessageOptions = { + embeds: [ + { + title: `About ${pluginData.client.user!.username}`, + fields: [ + { + name: "Status", + value: basicInfoRows.map(([label, value]) => `${label}: **${value}**`).join("\n"), + }, + { + name: `Loaded plugins on this server (${loadedPlugins.length})`, + value: loadedPlugins.join(", "), + }, + ], + }, + ], }; const supporters = await pluginData.state.supporters.getAll(); @@ -93,27 +88,28 @@ export const AboutCmd = utilityCmd({ ); if (supporters.length) { - aboutContent.embed.fields.push({ + aboutContent.embeds![0].fields!.push({ name: "Zeppelin supporters 🎉", value: supporters.map(s => `**${s.name}** ${s.amount ? `${s.amount}€/mo` : ""}`.trim()).join("\n"), + inline: false, }); } // For the embed color, find the highest colored role the bot has - this is their color on the server as well - const botMember = await resolveMember(pluginData.client, pluginData.guild, pluginData.client.user.id); - let botRoles = botMember?.roles.map(r => (msg.channel as GuildChannel).guild.roles.get(r)!) || []; + const botMember = await resolveMember(pluginData.client, pluginData.guild, pluginData.client.user!.id); + let botRoles = botMember?.roles.cache.map(r => (msg.channel as GuildChannel).guild.roles.cache.get(r.id)!) || []; botRoles = botRoles.filter(r => !!r); // Drop any unknown roles botRoles = botRoles.filter(r => r.color); // Filter to those with a color botRoles.sort(sorter("position", "DESC")); // Sort by position (highest first) if (botRoles.length) { - aboutContent.embed.color = botRoles[0].color; + aboutContent.embeds![0].color = botRoles[0].color; } // Use the bot avatar as the embed image - if (pluginData.client.user.avatarURL) { - aboutContent.embed.thumbnail = { url: pluginData.client.user.avatarURL }; + if (pluginData.client.user!.avatarURL()) { + aboutContent.embeds![0].thumbnail = { url: pluginData.client.user!.avatarURL()! }; } - msg.channel.createMessage(aboutContent); + msg.channel.send(aboutContent); }, }); diff --git a/backend/src/plugins/Utility/commands/AvatarCmd.ts b/backend/src/plugins/Utility/commands/AvatarCmd.ts index 7ab1fac8..d503bc97 100644 --- a/backend/src/plugins/Utility/commands/AvatarCmd.ts +++ b/backend/src/plugins/Utility/commands/AvatarCmd.ts @@ -1,8 +1,8 @@ -import { utilityCmd } from "../types"; +import { MessageEmbedOptions } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { UnknownUser } from "../../../utils"; import { sendErrorMessage } from "../../../pluginUtils"; -import { EmbedOptions } from "eris"; +import { UnknownUser } from "../../../utils"; +import { utilityCmd } from "../types"; export const AvatarCmd = utilityCmd({ trigger: ["avatar", "av"], @@ -16,15 +16,13 @@ export const AvatarCmd = utilityCmd({ async run({ message: msg, args, pluginData }) { const user = args.user || msg.author; if (!(user instanceof UnknownUser)) { - let extension = user.avatarURL.slice(user.avatarURL.lastIndexOf("."), user.avatarURL.lastIndexOf("?")); - // Some pngs can have the .jpg extention for some reason, so we always use .png for static images - extension = extension === ".gif" ? extension : ".png"; - const avatarUrl = user.avatarURL.slice(0, user.avatarURL.lastIndexOf(".")); - const embed: EmbedOptions = { - image: { url: avatarUrl + `${extension}?size=2048` }, + const embed: MessageEmbedOptions = { + image: { + url: user.displayAvatarURL({ dynamic: true, format: "png", size: 2048 }), + }, + title: `Avatar of ${user.tag}:`, }; - embed.title = `Avatar of ${user.username}#${user.discriminator}:`; - msg.channel.createMessage({ embed }); + msg.channel.send({ embeds: [embed] }); } else { sendErrorMessage(pluginData, msg.channel, "Invalid user ID"); } diff --git a/backend/src/plugins/Utility/commands/BanSearchCmd.ts b/backend/src/plugins/Utility/commands/BanSearchCmd.ts index df599b67..25d60fb4 100644 --- a/backend/src/plugins/Utility/commands/BanSearchCmd.ts +++ b/backend/src/plugins/Utility/commands/BanSearchCmd.ts @@ -1,6 +1,6 @@ -import { utilityCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { archiveSearch, displaySearch, SearchType } from "../search"; +import { utilityCmd } from "../types"; // Separate from BanSearchCmd to avoid a circular reference from ./search.ts export const banSearchSignature = { @@ -8,10 +8,10 @@ export const banSearchSignature = { page: ct.number({ option: true, shortcut: "p" }), sort: ct.string({ option: true }), - "case-sensitive": ct.switchOption({ shortcut: "cs" }), - export: ct.switchOption({ shortcut: "e" }), + "case-sensitive": ct.switchOption({ def: false, shortcut: "cs" }), + export: ct.switchOption({ def: false, shortcut: "e" }), ids: ct.switchOption(), - regex: ct.switchOption({ shortcut: "re" }), + regex: ct.switchOption({ def: false, shortcut: "re" }), }; export const BanSearchCmd = utilityCmd({ diff --git a/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts b/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts index 044ba63d..bbef9bcf 100644 --- a/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts @@ -1,7 +1,7 @@ -import { utilityCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage } from "../../../pluginUtils"; import { getChannelInfoEmbed } from "../functions/getChannelInfoEmbed"; +import { utilityCmd } from "../types"; export const ChannelInfoCmd = utilityCmd({ trigger: ["channel", "channelinfo"], @@ -20,6 +20,6 @@ export const ChannelInfoCmd = utilityCmd({ return; } - message.channel.createMessage({ embed }); + message.channel.send({ embeds: [embed] }); }, }); diff --git a/backend/src/plugins/Utility/commands/CleanCmd.ts b/backend/src/plugins/Utility/commands/CleanCmd.ts index 992a8b8a..c7f8e6c3 100644 --- a/backend/src/plugins/Utility/commands/CleanCmd.ts +++ b/backend/src/plugins/Utility/commands/CleanCmd.ts @@ -1,20 +1,22 @@ -import { utilityCmd, UtilityPluginType } from "../types"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { DAYS, getInviteCodesInString, noop, SECONDS, stripObjectToScalars } from "../../../utils"; -import { getBaseUrl, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { Message, TextChannel, User } from "eris"; -import moment from "moment-timezone"; +import { Message, Snowflake, TextChannel, User } from "discord.js"; import { GuildPluginData } from "knub"; +import moment from "moment-timezone"; +import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; import { SavedMessage } from "../../../data/entities/SavedMessage"; import { LogType } from "../../../data/LogType"; -import { allowTimeout } from "../../../RegExpRunner"; import { ModActionsPlugin } from "../../../plugins/ModActions/ModActionsPlugin"; +import { getBaseUrl, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { allowTimeout } from "../../../RegExpRunner"; +import { DAYS, getInviteCodesInString, noop, SECONDS } from "../../../utils"; +import { utilityCmd, UtilityPluginType } from "../types"; +import { boolean, number } from "io-ts"; const MAX_CLEAN_COUNT = 150; const MAX_CLEAN_TIME = 1 * DAYS; const CLEAN_COMMAND_DELETE_DELAY = 5 * SECONDS; -async function cleanMessages( +export async function cleanMessages( pluginData: GuildPluginData, channel: TextChannel, savedMessages: SavedMessage[], @@ -25,14 +27,14 @@ async function cleanMessages( // Delete & archive in ID order savedMessages = Array.from(savedMessages).sort((a, b) => (a.id > b.id ? 1 : -1)); - const idsToDelete = savedMessages.map(m => m.id); + const idsToDelete = savedMessages.map(m => m.id) as Snowflake[]; // Make sure the deletions aren't double logged idsToDelete.forEach(id => pluginData.state.logs.ignoreLog(LogType.MESSAGE_DELETE, id)); pluginData.state.logs.ignoreLog(LogType.MESSAGE_DELETE_BULK, idsToDelete[0]); // Actually delete the messages - await pluginData.client.deleteMessages(channel.id, idsToDelete); + channel.bulkDelete(idsToDelete); await pluginData.state.savedMessages.markBulkAsDeleted(idsToDelete); // Create an archive @@ -41,8 +43,8 @@ async function cleanMessages( const archiveUrl = pluginData.state.archives.getUrl(baseUrl, archiveId); pluginData.state.logs.log(LogType.CLEAN, { - mod: stripObjectToScalars(mod), - channel: stripObjectToScalars(channel), + mod: userToConfigAccessibleUser(mod), + channel: channelToConfigAccessibleChannel(channel), count: savedMessages.length, archiveUrl, }); @@ -53,13 +55,149 @@ async function cleanMessages( const opts = { user: ct.userId({ option: true, shortcut: "u" }), channel: ct.channelId({ option: true, shortcut: "c" }), - bots: ct.switchOption({ shortcut: "b" }), - "delete-pins": ct.switchOption({ shortcut: "p" }), - "has-invites": ct.switchOption({ shortcut: "i" }), + bots: ct.switchOption({ def: false, shortcut: "b" }), + "delete-pins": ct.switchOption({ def: false, shortcut: "p" }), + "has-invites": ct.switchOption({ def: false, shortcut: "i" }), match: ct.regex({ option: true, shortcut: "m" }), "to-id": ct.anyId({ option: true, shortcut: "id" }), }; +export interface CleanArgs { + count: number; + update?: boolean; + user?: string; + channel?: string; + bots?: boolean; + "delete-pins"?: boolean; + "has-invites"?: boolean; + match?: RegExp; + "to-id"?: string; +} + +export async function cleanCmd(pluginData: GuildPluginData, args: CleanArgs | any, msg) { + if (args.count > MAX_CLEAN_COUNT || args.count <= 0) { + sendErrorMessage(pluginData, msg.channel, `Clean count must be between 1 and ${MAX_CLEAN_COUNT}`); + return; + } + + const targetChannel = args.channel ? pluginData.guild.channels.cache.get(args.channel as Snowflake) : msg.channel; + if (!targetChannel || !(targetChannel instanceof TextChannel)) { + sendErrorMessage(pluginData, msg.channel, `Invalid channel specified`); + return; + } + + if (targetChannel.id !== msg.channel.id) { + const configForTargetChannel = await pluginData.config.getMatchingConfig({ + userId: msg.author.id, + member: msg.member, + channelId: targetChannel.id, + categoryId: targetChannel.parentId, + }); + if (configForTargetChannel.can_clean !== true) { + sendErrorMessage(pluginData, msg.channel, `Missing permissions to use clean on that channel`); + return; + } + } + + const cleaningMessage = msg.channel.send("Cleaning..."); + + const messagesToClean: SavedMessage[] = []; + let beforeId = msg.id; + const timeCutoff = msg.createdTimestamp - MAX_CLEAN_TIME; + const upToMsgId = args["to-id"]; + let foundId = false; + + const deletePins = args["delete-pins"] != null ? args["delete-pins"] : false; + let pins: Message[] = []; + if (!deletePins) { + pins = [...(await msg.channel.messages.fetchPinned().catch(() => [])).values()]; + } + + while (messagesToClean.length < args.count) { + const potentialMessages = await targetChannel.messages.fetch({ + before: beforeId, + limit: args.count, + }); + if (potentialMessages.size === 0) break; + + const existingStored = await pluginData.state.savedMessages.getMultiple([...potentialMessages.keys()]); + const alreadyStored = existingStored.map(stored => stored.id); + const messagesToStore = [ + ...potentialMessages.filter(potentialMsg => !alreadyStored.includes(potentialMsg.id)).values(), + ]; + await pluginData.state.savedMessages.createFromMessages(messagesToStore); + + const potentialMessagesToClean = await pluginData.state.savedMessages.getMultiple([...potentialMessages.keys()]); + if (potentialMessagesToClean.length === 0) break; + + const filtered: SavedMessage[] = []; + for (const message of potentialMessagesToClean) { + const contentString = message.data.content || ""; + if (args.user && message.user_id !== args.user) continue; + if (args.bots && !message.is_bot) continue; + if (!deletePins && pins.find(x => x.id === message.id) != null) continue; + if (args["has-invites"] && getInviteCodesInString(contentString).length === 0) continue; + if (upToMsgId != null && message.id < upToMsgId) { + foundId = true; + break; + } + if (moment.utc(message.posted_at).valueOf() < timeCutoff) continue; + if (args.match && !(await pluginData.state.regexRunner.exec(args.match, contentString).catch(allowTimeout))) { + continue; + } + + filtered.push(message); + } + const remaining = args.count - messagesToClean.length; + const withoutOverflow = filtered.slice(0, remaining); + messagesToClean.push(...withoutOverflow); + + beforeId = potentialMessages.lastKey()!; + + if (foundId || moment.utc(potentialMessages.last()!.createdTimestamp).valueOf() < timeCutoff) { + break; + } + } + + let responseMsg: Message | undefined; + if (messagesToClean.length > 0) { + const cleanResult = await cleanMessages(pluginData, targetChannel, messagesToClean, msg.author); + + let responseText = `Cleaned ${messagesToClean.length} ${messagesToClean.length === 1 ? "message" : "messages"}`; + if (targetChannel.id !== msg.channel.id) { + responseText += ` in <#${targetChannel.id}>: ${cleanResult.archiveUrl}`; + } + + if (args.update) { + const modActions = pluginData.getPlugin(ModActionsPlugin); + const channelId = targetChannel.id !== msg.channel.id ? targetChannel.id : msg.channel.id; + const updateMessage = `Cleaned ${messagesToClean.length} ${ + messagesToClean.length === 1 ? "message" : "messages" + } in <#${channelId}>: ${cleanResult.archiveUrl}`; + if (typeof args.update === "number") { + modActions.updateCase(msg, args.update, updateMessage); + } else { + modActions.updateCase(msg, null, updateMessage); + } + } + + responseMsg = await sendSuccessMessage(pluginData, msg.channel, responseText); + } else { + responseMsg = await sendErrorMessage(pluginData, msg.channel, `Found no messages to clean!`); + } + + await (await cleaningMessage).delete(); + + if (targetChannel.id === msg.channel.id) { + // Delete the !clean command and the bot response if a different channel wasn't specified + // (so as not to spam the cleaned channel with the command itself) + setTimeout(() => { + msg.delete().catch(noop); + responseMsg?.delete().catch(noop); + }, CLEAN_COMMAND_DELETE_DELAY); + } +} + export const CleanCmd = utilityCmd({ trigger: ["clean", "clear"], description: "Remove a number of recent messages", @@ -75,127 +213,13 @@ export const CleanCmd = utilityCmd({ }, { count: ct.number(), - update: ct.switchOption({ shortcut: "up" }), + update: ct.switchOption({ def: false, shortcut: "up" }), ...opts, }, ], async run({ message: msg, args, pluginData }) { - if (args.count > MAX_CLEAN_COUNT || args.count <= 0) { - sendErrorMessage(pluginData, msg.channel, `Clean count must be between 1 and ${MAX_CLEAN_COUNT}`); - return; - } - - const targetChannel = args.channel ? pluginData.guild.channels.get(args.channel) : msg.channel; - if (!targetChannel || !(targetChannel instanceof TextChannel)) { - sendErrorMessage(pluginData, msg.channel, `Invalid channel specified`); - return; - } - - if (targetChannel.id !== msg.channel.id) { - const configForTargetChannel = await pluginData.config.getMatchingConfig({ - userId: msg.author.id, - member: msg.member, - channelId: targetChannel.id, - categoryId: targetChannel.parentID, - }); - if (configForTargetChannel.can_clean !== true) { - sendErrorMessage(pluginData, msg.channel, `Missing permissions to use clean on that channel`); - return; - } - } - - const cleaningMessage = msg.channel.createMessage("Cleaning..."); - - const messagesToClean: SavedMessage[] = []; - let beforeId = msg.id; - const timeCutoff = msg.timestamp - MAX_CLEAN_TIME; - const upToMsgId = args["to-id"]; - let foundId = false; - - const deletePins = args["delete-pins"] != null ? args["delete-pins"] : false; - let pins: Message[] = []; - if (!deletePins) { - pins = await msg.channel.getPins(); - } - - while (messagesToClean.length < args.count) { - const potentialMessagesToClean = await pluginData.state.savedMessages.getLatestByChannelBeforeId( - targetChannel.id, - beforeId, - args.count, - ); - if (potentialMessagesToClean.length === 0) break; - - const filtered: SavedMessage[] = []; - for (const message of potentialMessagesToClean) { - const contentString = message.data.content || ""; - if (args.user && message.user_id !== args.user) continue; - if (args.bots && !message.is_bot) continue; - if (!deletePins && pins.find(x => x.id === message.id) != null) continue; - if (args["has-invites"] && getInviteCodesInString(contentString).length === 0) continue; - if (upToMsgId != null && message.id < upToMsgId) { - foundId = true; - break; - } - if (moment.utc(message.posted_at).valueOf() < timeCutoff) continue; - if (args.match && !(await pluginData.state.regexRunner.exec(args.match, contentString).catch(allowTimeout))) { - continue; - } - - filtered.push(message); - } - const remaining = args.count - messagesToClean.length; - const withoutOverflow = filtered.slice(0, remaining); - messagesToClean.push(...withoutOverflow); - - beforeId = potentialMessagesToClean[potentialMessagesToClean.length - 1].id; - - if ( - foundId || - moment.utc(potentialMessagesToClean[potentialMessagesToClean.length - 1].posted_at).valueOf() < timeCutoff - ) { - break; - } - } - - let responseMsg: Message | undefined; - if (messagesToClean.length > 0) { - const cleanResult = await cleanMessages(pluginData, targetChannel, messagesToClean, msg.author); - - let responseText = `Cleaned ${messagesToClean.length} ${messagesToClean.length === 1 ? "message" : "messages"}`; - if (targetChannel.id !== msg.channel.id) { - responseText += ` in <#${targetChannel.id}>\n${cleanResult.archiveUrl}`; - } - - if (args.update) { - const modActions = pluginData.getPlugin(ModActionsPlugin); - const channelId = targetChannel.id !== msg.channel.id ? targetChannel.id : msg.channel.id; - const updateMessage = `Cleaned ${messagesToClean.length} ${ - messagesToClean.length === 1 ? "message" : "messages" - } in <#${channelId}>: ${cleanResult.archiveUrl}`; - if (typeof args.update === "number") { - modActions.updateCase(msg, args.update, updateMessage); - } else { - modActions.updateCase(msg, null, updateMessage); - } - } - - responseMsg = await sendSuccessMessage(pluginData, msg.channel, responseText); - } else { - responseMsg = await sendErrorMessage(pluginData, msg.channel, `Found no messages to clean!`); - } - - await (await cleaningMessage).delete(); - - if (targetChannel.id === msg.channel.id) { - // Delete the !clean command and the bot response if a different channel wasn't specified - // (so as not to spam the cleaned channel with the command itself) - setTimeout(() => { - msg.delete().catch(noop); - responseMsg?.delete().catch(noop); - }, CLEAN_COMMAND_DELETE_DELAY); - } + cleanCmd(pluginData, args, msg); }, }); diff --git a/backend/src/plugins/Utility/commands/ContextCmd.ts b/backend/src/plugins/Utility/commands/ContextCmd.ts index 81a488b3..ceec4407 100644 --- a/backend/src/plugins/Utility/commands/ContextCmd.ts +++ b/backend/src/plugins/Utility/commands/ContextCmd.ts @@ -1,9 +1,9 @@ -import { utilityCmd } from "../types"; +import { Snowflake, TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { messageLink } from "../../../utils"; import { sendErrorMessage } from "../../../pluginUtils"; -import { TextChannel } from "eris"; +import { messageLink } from "../../../utils"; import { canReadChannel } from "../../../utils/canReadChannel"; +import { utilityCmd } from "../types"; export const ContextCmd = utilityCmd({ trigger: "context", @@ -35,12 +35,17 @@ export const ContextCmd = utilityCmd({ return; } - const previousMessage = (await pluginData.client.getMessages(channel.id, 1, messageId))[0]; + const previousMessage = ( + await (pluginData.guild.channels.cache.get(channel.id) as TextChannel).messages.fetch({ + limit: 1, + before: messageId as Snowflake, + }) + )[0]; if (!previousMessage) { sendErrorMessage(pluginData, msg.channel, "Message context not found"); return; } - msg.channel.createMessage(messageLink(pluginData.guild.id, previousMessage.channel.id, previousMessage.id)); + msg.channel.send(messageLink(pluginData.guild.id, previousMessage.channel.id, previousMessage.id)); }, }); diff --git a/backend/src/plugins/Utility/commands/EmojiInfoCmd.ts b/backend/src/plugins/Utility/commands/EmojiInfoCmd.ts index 41889509..3f7d094b 100644 --- a/backend/src/plugins/Utility/commands/EmojiInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/EmojiInfoCmd.ts @@ -1,8 +1,8 @@ -import { utilityCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage } from "../../../pluginUtils"; import { customEmojiRegex } from "../../../utils"; import { getEmojiInfoEmbed } from "../functions/getEmojiInfoEmbed"; +import { utilityCmd } from "../types"; export const EmojiInfoCmd = utilityCmd({ trigger: ["emoji", "emojiinfo"], @@ -27,6 +27,6 @@ export const EmojiInfoCmd = utilityCmd({ return; } - message.channel.createMessage({ embed }); + message.channel.send({ embeds: [embed] }); }, }); diff --git a/backend/src/plugins/Utility/commands/HelpCmd.ts b/backend/src/plugins/Utility/commands/HelpCmd.ts index 8d4af9cc..112e932e 100644 --- a/backend/src/plugins/Utility/commands/HelpCmd.ts +++ b/backend/src/plugins/Utility/commands/HelpCmd.ts @@ -1,8 +1,8 @@ -import { utilityCmd } from "../types"; +import { LoadedGuildPlugin } from "knub"; +import { PluginCommandDefinition } from "knub/dist/commands/commandUtils"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { createChunkedMessage } from "../../../utils"; -import { PluginCommandDefinition } from "knub/dist/commands/commandUtils"; -import { LoadedGuildPlugin } from "knub"; +import { utilityCmd } from "../types"; export const HelpCmd = utilityCmd({ trigger: "help", @@ -71,7 +71,7 @@ export const HelpCmd = utilityCmd({ }); if (totalResults === 0) { - msg.channel.createMessage("No matching commands found!"); + msg.channel.send("No matching commands found!"); return; } @@ -80,7 +80,7 @@ export const HelpCmd = utilityCmd({ ? `Results (${totalResults} total, showing first ${limitedResults.length}):\n\n` : ""; - message += `${commandSnippets.join("\n\n")}`; + message += commandSnippets.join("\n\n"); createChunkedMessage(msg.channel, message); }, }); diff --git a/backend/src/plugins/Utility/commands/InfoCmd.ts b/backend/src/plugins/Utility/commands/InfoCmd.ts index 6e84e3d3..9c4b55d3 100644 --- a/backend/src/plugins/Utility/commands/InfoCmd.ts +++ b/backend/src/plugins/Utility/commands/InfoCmd.ts @@ -1,19 +1,27 @@ -import { utilityCmd } from "../types"; +import { Snowflake } from "discord.js"; +import { getChannelId, getRoleId } from "knub/dist/utils"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage } from "../../../pluginUtils"; -import { getInviteInfoEmbed } from "../functions/getInviteInfoEmbed"; -import { customEmojiRegex, isValidSnowflake, parseInviteCodeInput, resolveInvite, resolveUser } from "../../../utils"; -import { getUserInfoEmbed } from "../functions/getUserInfoEmbed"; -import { resolveMessageTarget } from "../../../utils/resolveMessageTarget"; +import { + customEmojiRegex, + isValidSnowflake, + noop, + parseInviteCodeInput, + resolveInvite, + resolveUser, +} from "../../../utils"; import { canReadChannel } from "../../../utils/canReadChannel"; -import { getMessageInfoEmbed } from "../functions/getMessageInfoEmbed"; +import { resolveMessageTarget } from "../../../utils/resolveMessageTarget"; import { getChannelInfoEmbed } from "../functions/getChannelInfoEmbed"; -import { getServerInfoEmbed } from "../functions/getServerInfoEmbed"; -import { getChannelId, getRoleId } from "knub/dist/utils"; -import { getGuildPreview } from "../functions/getGuildPreview"; -import { getSnowflakeInfoEmbed } from "../functions/getSnowflakeInfoEmbed"; -import { getRoleInfoEmbed } from "../functions/getRoleInfoEmbed"; import { getEmojiInfoEmbed } from "../functions/getEmojiInfoEmbed"; +import { getGuildPreview } from "../functions/getGuildPreview"; +import { getInviteInfoEmbed } from "../functions/getInviteInfoEmbed"; +import { getMessageInfoEmbed } from "../functions/getMessageInfoEmbed"; +import { getRoleInfoEmbed } from "../functions/getRoleInfoEmbed"; +import { getServerInfoEmbed } from "../functions/getServerInfoEmbed"; +import { getSnowflakeInfoEmbed } from "../functions/getSnowflakeInfoEmbed"; +import { getUserInfoEmbed } from "../functions/getUserInfoEmbed"; +import { utilityCmd } from "../types"; export const InfoCmd = utilityCmd({ trigger: "info", @@ -24,7 +32,7 @@ export const InfoCmd = utilityCmd({ signature: { value: ct.string({ required: false }), - compact: ct.switchOption({ shortcut: "c" }), + compact: ct.switchOption({ def: false, shortcut: "c" }), }, async run({ message, args, pluginData }) { @@ -38,11 +46,11 @@ export const InfoCmd = utilityCmd({ // 1. Channel if (userCfg.can_channelinfo) { const channelId = getChannelId(value); - const channel = channelId && pluginData.guild.channels.get(channelId); + const channel = channelId && pluginData.guild.channels.cache.get(channelId as Snowflake); if (channel) { const embed = await getChannelInfoEmbed(pluginData, channelId!, message.author.id); if (embed) { - message.channel.createMessage({ embed }); + message.channel.send({ embeds: [embed] }); return; } } @@ -50,11 +58,11 @@ export const InfoCmd = utilityCmd({ // 2. Server if (userCfg.can_server) { - const guild = pluginData.client.guilds.get(value); + const guild = await pluginData.client.guilds.fetch(value as Snowflake).catch(noop); if (guild) { const embed = await getServerInfoEmbed(pluginData, value, message.author.id); if (embed) { - message.channel.createMessage({ embed }); + message.channel.send({ embeds: [embed] }); return; } } @@ -66,7 +74,7 @@ export const InfoCmd = utilityCmd({ if (user && userCfg.can_userinfo) { const embed = await getUserInfoEmbed(pluginData, user.id, Boolean(args.compact), message.author.id); if (embed) { - message.channel.createMessage({ embed }); + message.channel.send({ embeds: [embed] }); return; } } @@ -84,7 +92,7 @@ export const InfoCmd = utilityCmd({ message.author.id, ); if (embed) { - message.channel.createMessage({ embed }); + message.channel.send({ embeds: [embed] }); return; } } @@ -99,7 +107,7 @@ export const InfoCmd = utilityCmd({ if (invite) { const embed = await getInviteInfoEmbed(pluginData, inviteCode); if (embed) { - message.channel.createMessage({ embed }); + message.channel.send({ embeds: [embed] }); return; } } @@ -108,11 +116,11 @@ export const InfoCmd = utilityCmd({ // 6. Server again (fallback for discovery servers) if (userCfg.can_server) { - const serverPreview = getGuildPreview(pluginData.client, value).catch(() => null); + const serverPreview = await getGuildPreview(pluginData.client, value).catch(() => null); if (serverPreview) { const embed = await getServerInfoEmbed(pluginData, value, message.author.id); if (embed) { - message.channel.createMessage({ embed }); + message.channel.send({ embeds: [embed] }); return; } } @@ -121,10 +129,10 @@ export const InfoCmd = utilityCmd({ // 7. Role if (userCfg.can_roleinfo) { const roleId = getRoleId(value); - const role = roleId && pluginData.guild.roles.get(roleId); + const role = roleId && pluginData.guild.roles.cache.get(roleId as Snowflake); if (role) { const embed = await getRoleInfoEmbed(pluginData, role, message.author.id); - message.channel.createMessage({ embed }); + message.channel.send({ embeds: [embed] }); return; } } @@ -135,7 +143,7 @@ export const InfoCmd = utilityCmd({ if (emojiIdMatch?.[2]) { const embed = await getEmojiInfoEmbed(pluginData, emojiIdMatch[2]); if (embed) { - message.channel.createMessage({ embed }); + message.channel.send({ embeds: [embed] }); return; } } @@ -144,7 +152,7 @@ export const InfoCmd = utilityCmd({ // 9. Arbitrary ID if (isValidSnowflake(value) && userCfg.can_snowflake) { const embed = await getSnowflakeInfoEmbed(pluginData, value, true, message.author.id); - message.channel.createMessage({ embed }); + message.channel.send({ embeds: [embed] }); return; } diff --git a/backend/src/plugins/Utility/commands/InviteInfoCmd.ts b/backend/src/plugins/Utility/commands/InviteInfoCmd.ts index ee876026..df34123a 100644 --- a/backend/src/plugins/Utility/commands/InviteInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/InviteInfoCmd.ts @@ -1,8 +1,8 @@ -import { utilityCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage } from "../../../pluginUtils"; -import { getInviteInfoEmbed } from "../functions/getInviteInfoEmbed"; import { parseInviteCodeInput } from "../../../utils"; +import { getInviteInfoEmbed } from "../functions/getInviteInfoEmbed"; +import { utilityCmd } from "../types"; export const InviteInfoCmd = utilityCmd({ trigger: ["invite", "inviteinfo"], @@ -22,6 +22,6 @@ export const InviteInfoCmd = utilityCmd({ return; } - message.channel.createMessage({ embed }); + message.channel.send({ embeds: [embed] }); }, }); diff --git a/backend/src/plugins/Utility/commands/JumboCmd.ts b/backend/src/plugins/Utility/commands/JumboCmd.ts index d31e2dcb..bc117d45 100644 --- a/backend/src/plugins/Utility/commands/JumboCmd.ts +++ b/backend/src/plugins/Utility/commands/JumboCmd.ts @@ -1,10 +1,11 @@ -import { utilityCmd } from "../types"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { downloadFile, isEmoji, SECONDS } from "../../../utils"; +import { MessageAttachment } from "discord.js"; import fs from "fs"; import sharp from "sharp"; import twemoji from "twemoji"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage } from "../../../pluginUtils"; +import { downloadFile, isEmoji, SECONDS } from "../../../utils"; +import { utilityCmd } from "../types"; const fsp = fs.promises; @@ -39,8 +40,8 @@ export const JumboCmd = utilityCmd({ const size = config.jumbo_size > 2048 ? 2048 : config.jumbo_size; const emojiRegex = new RegExp(`(<.*:).*:(\\d+)`); const results = emojiRegex.exec(args.emoji); - let extention = ".png"; - let file; + let extension = ".png"; + let file: MessageAttachment | undefined; if (!isEmoji(args.emoji)) { sendErrorMessage(pluginData, msg.channel, "Invalid emoji"); @@ -50,25 +51,19 @@ export const JumboCmd = utilityCmd({ if (results) { let url = "https://cdn.discordapp.com/emojis/"; if (results[1] === " 32) { - msg.channel.createMessage(errorMessage("Nickname must be between 2 and 32 characters long")); + msg.channel.send(errorMessage("Nickname must be between 2 and 32 characters long")); return; } - const oldNickname = args.member.nick || ""; + const oldNickname = args.member.nickname || ""; try { await args.member.edit({ nick: args.nickname, }); } catch { - msg.channel.createMessage(errorMessage("Failed to change nickname")); + msg.channel.send(errorMessage("Failed to change nickname")); return; } diff --git a/backend/src/plugins/Utility/commands/NicknameResetCmd.ts b/backend/src/plugins/Utility/commands/NicknameResetCmd.ts index 33085053..91894a40 100644 --- a/backend/src/plugins/Utility/commands/NicknameResetCmd.ts +++ b/backend/src/plugins/Utility/commands/NicknameResetCmd.ts @@ -1,7 +1,7 @@ -import { utilityCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { errorMessage } from "../../../utils"; import { canActOn, sendSuccessMessage } from "../../../pluginUtils"; +import { errorMessage } from "../../../utils"; +import { utilityCmd } from "../types"; export const NicknameResetCmd = utilityCmd({ trigger: ["nickname reset", "nick reset"], @@ -15,7 +15,7 @@ export const NicknameResetCmd = utilityCmd({ async run({ message: msg, args, pluginData }) { if (msg.member.id !== args.member.id && !canActOn(pluginData, msg.member, args.member)) { - msg.channel.createMessage(errorMessage("Cannot reset nickname: insufficient permissions")); + msg.channel.send(errorMessage("Cannot reset nickname: insufficient permissions")); return; } @@ -24,7 +24,7 @@ export const NicknameResetCmd = utilityCmd({ nick: "", }); } catch { - msg.channel.createMessage(errorMessage("Failed to reset nickname")); + msg.channel.send(errorMessage("Failed to reset nickname")); return; } diff --git a/backend/src/plugins/Utility/commands/PingCmd.ts b/backend/src/plugins/Utility/commands/PingCmd.ts index b8304182..842d3742 100644 --- a/backend/src/plugins/Utility/commands/PingCmd.ts +++ b/backend/src/plugins/Utility/commands/PingCmd.ts @@ -1,6 +1,6 @@ -import { utilityCmd } from "../types"; +import { Message } from "discord.js"; import { noop, trimLines } from "../../../utils"; -import { Message } from "eris"; +import { utilityCmd } from "../types"; const { performance } = require("perf_hooks"); @@ -16,12 +16,12 @@ export const PingCmd = utilityCmd({ for (let i = 0; i < 4; i++) { const start = performance.now(); - const message = await msg.channel.createMessage(`Calculating ping... ${i + 1}`); + const message = await msg.channel.send(`Calculating ping... ${i + 1}`); times.push(performance.now() - start); messages.push(message); if (msgToMsgDelay === undefined) { - msgToMsgDelay = message.timestamp - msg.timestamp; + msgToMsgDelay = message.createdTimestamp - msg.createdTimestamp; } } @@ -29,25 +29,18 @@ export const PingCmd = utilityCmd({ const lowest = Math.round(Math.min(...times)); const mean = Math.round(times.reduce((total, ms) => total + ms, 0) / times.length); - const shard = pluginData.client.shards.get(pluginData.client.guildShardMap[pluginData.guild.id])!; - - msg.channel.createMessage( + msg.channel.send( trimLines(` **Ping:** Lowest: **${lowest}ms** Highest: **${highest}ms** Mean: **${mean}ms** Time between ping command and first reply: **${msgToMsgDelay!}ms** - Shard latency: **${shard.latency}ms** + Shard latency: **${pluginData.client.ws.ping}ms** `), ); // Clean up test messages - pluginData.client - .deleteMessages( - messages[0].channel.id, - messages.map(m => m.id), - ) - .catch(noop); + msg.channel.bulkDelete(messages).catch(noop); }, }); diff --git a/backend/src/plugins/Utility/commands/ReloadGuildCmd.ts b/backend/src/plugins/Utility/commands/ReloadGuildCmd.ts index e6a3074d..48734fe0 100644 --- a/backend/src/plugins/Utility/commands/ReloadGuildCmd.ts +++ b/backend/src/plugins/Utility/commands/ReloadGuildCmd.ts @@ -1,6 +1,6 @@ -import { utilityCmd } from "../types"; -import { TextChannel } from "eris"; +import { TextChannel } from "discord.js"; import { activeReloads } from "../guildReloads"; +import { utilityCmd } from "../types"; export const ReloadGuildCmd = utilityCmd({ trigger: "reload_guild", @@ -11,7 +11,7 @@ export const ReloadGuildCmd = utilityCmd({ if (activeReloads.has(pluginData.guild.id)) return; activeReloads.set(pluginData.guild.id, msg.channel as TextChannel); - msg.channel.createMessage("Reloading..."); + msg.channel.send("Reloading..."); pluginData.getKnubInstance().reloadGuild(pluginData.guild.id); }, }); diff --git a/backend/src/plugins/Utility/commands/RoleInfoCmd.ts b/backend/src/plugins/Utility/commands/RoleInfoCmd.ts index 1b04bf9b..b04b49a7 100644 --- a/backend/src/plugins/Utility/commands/RoleInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/RoleInfoCmd.ts @@ -1,7 +1,6 @@ -import { utilityCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; import { getRoleInfoEmbed } from "../functions/getRoleInfoEmbed"; +import { utilityCmd } from "../types"; export const RoleInfoCmd = utilityCmd({ trigger: ["roleinfo"], @@ -15,6 +14,6 @@ export const RoleInfoCmd = utilityCmd({ async run({ message, args, pluginData }) { const embed = await getRoleInfoEmbed(pluginData, args.role, message.author.id); - message.channel.createMessage({ embed }); + message.channel.send({ embeds: [embed] }); }, }); diff --git a/backend/src/plugins/Utility/commands/RolesCmd.ts b/backend/src/plugins/Utility/commands/RolesCmd.ts index 5aa52452..2aa3f459 100644 --- a/backend/src/plugins/Utility/commands/RolesCmd.ts +++ b/backend/src/plugins/Utility/commands/RolesCmd.ts @@ -1,9 +1,9 @@ -import { utilityCmd } from "../types"; +import { Role, TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { Role, TextChannel } from "eris"; +import { sendErrorMessage } from "../../../pluginUtils"; import { chunkArray, sorter, trimLines } from "../../../utils"; import { refreshMembersIfNeeded } from "../refreshMembers"; -import { sendErrorMessage } from "../../../pluginUtils"; +import { utilityCmd } from "../types"; export const RolesCmd = utilityCmd({ trigger: "roles", @@ -21,7 +21,9 @@ export const RolesCmd = utilityCmd({ async run({ message: msg, args, pluginData }) { const { guild } = pluginData; - let roles: Array<{ _memberCount?: number } & Role> = Array.from((msg.channel as TextChannel).guild.roles.values()); + let roles: Array<{ _memberCount?: number } & Role> = Array.from( + (msg.channel as TextChannel).guild.roles.cache.values(), + ); let sort = args.sort; if (args.search) { @@ -32,13 +34,9 @@ export const RolesCmd = utilityCmd({ if (args.counts) { await refreshMembersIfNeeded(guild); - // If the user requested role member counts as well, calculate them and sort the roles by their member count - const roleCounts: Map = Array.from(guild.members.values()).reduce((map, member) => { - for (const roleId of member.roles) { - if (!map.has(roleId)) map.set(roleId, 0); - map.set(roleId, map.get(roleId) + 1); - } - + // If the user requested role member counts as well, fetch them and sort the roles by their member count + const roleCounts: Map = Array.from(guild.roles.cache.values()).reduce((map, role) => { + map.set(role.id, role.members.size); return map; }, new Map()); @@ -97,14 +95,14 @@ export const RolesCmd = utilityCmd({ }); if (i === 0) { - msg.channel.createMessage( + msg.channel.send( trimLines(` ${args.search ? "Total roles found" : "Total roles"}: ${roles.length} \`\`\`py\n${roleLines.join("\n")}\`\`\` `), ); } else { - msg.channel.createMessage("```py\n" + roleLines.join("\n") + "```"); + msg.channel.send("```py\n" + roleLines.join("\n") + "```"); } } }, diff --git a/backend/src/plugins/Utility/commands/SearchCmd.ts b/backend/src/plugins/Utility/commands/SearchCmd.ts index 4b8038db..777bb890 100644 --- a/backend/src/plugins/Utility/commands/SearchCmd.ts +++ b/backend/src/plugins/Utility/commands/SearchCmd.ts @@ -1,6 +1,6 @@ -import { utilityCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { archiveSearch, displaySearch, SearchType } from "../search"; +import { utilityCmd } from "../types"; // Separate from SearchCmd to avoid a circular reference from ./search.ts export const searchCmdSignature = { @@ -8,14 +8,14 @@ export const searchCmdSignature = { page: ct.number({ option: true, shortcut: "p" }), role: ct.string({ option: true, shortcut: "r" }), - voice: ct.switchOption({ shortcut: "v" }), - bot: ct.switchOption({ shortcut: "b" }), + voice: ct.switchOption({ def: false, shortcut: "v" }), + bot: ct.switchOption({ def: false, shortcut: "b" }), sort: ct.string({ option: true }), - "case-sensitive": ct.switchOption({ shortcut: "cs" }), - export: ct.switchOption({ shortcut: "e" }), + "case-sensitive": ct.switchOption({ def: false, shortcut: "cs" }), + export: ct.switchOption({ def: false, shortcut: "e" }), ids: ct.switchOption(), - regex: ct.switchOption({ shortcut: "re" }), - "status-search": ct.switchOption({ shortcut: "ss" }), + regex: ct.switchOption({ def: false, shortcut: "re" }), + "status-search": ct.switchOption({ def: false, shortcut: "ss" }), }; export const SearchCmd = utilityCmd({ diff --git a/backend/src/plugins/Utility/commands/ServerInfoCmd.ts b/backend/src/plugins/Utility/commands/ServerInfoCmd.ts index 97a5d06a..30fb7efe 100644 --- a/backend/src/plugins/Utility/commands/ServerInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/ServerInfoCmd.ts @@ -1,7 +1,7 @@ -import { utilityCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage } from "../../../pluginUtils"; import { getServerInfoEmbed } from "../functions/getServerInfoEmbed"; +import { utilityCmd } from "../types"; export const ServerInfoCmd = utilityCmd({ trigger: ["server", "serverinfo"], @@ -21,6 +21,6 @@ export const ServerInfoCmd = utilityCmd({ return; } - message.channel.createMessage({ embed: serverInfoEmbed }); + message.channel.send({ embeds: [serverInfoEmbed] }); }, }); diff --git a/backend/src/plugins/Utility/commands/SnowflakeInfoCmd.ts b/backend/src/plugins/Utility/commands/SnowflakeInfoCmd.ts index ee03044e..59f43893 100644 --- a/backend/src/plugins/Utility/commands/SnowflakeInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/SnowflakeInfoCmd.ts @@ -1,8 +1,6 @@ -import { utilityCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendErrorMessage } from "../../../pluginUtils"; -import { getChannelInfoEmbed } from "../functions/getChannelInfoEmbed"; import { getSnowflakeInfoEmbed } from "../functions/getSnowflakeInfoEmbed"; +import { utilityCmd } from "../types"; export const SnowflakeInfoCmd = utilityCmd({ trigger: ["snowflake", "snowflakeinfo"], @@ -16,6 +14,6 @@ export const SnowflakeInfoCmd = utilityCmd({ async run({ message, args, pluginData }) { const embed = await getSnowflakeInfoEmbed(pluginData, args.id, false, message.author.id); - message.channel.createMessage({ embed }); + message.channel.send({ embeds: [embed] }); }, }); diff --git a/backend/src/plugins/Utility/commands/SourceCmd.ts b/backend/src/plugins/Utility/commands/SourceCmd.ts index 47ae3658..ada75479 100644 --- a/backend/src/plugins/Utility/commands/SourceCmd.ts +++ b/backend/src/plugins/Utility/commands/SourceCmd.ts @@ -1,10 +1,9 @@ -import { utilityCmd } from "../types"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { errorMessage } from "../../../utils"; -import { getBaseUrl, sendErrorMessage } from "../../../pluginUtils"; +import { Snowflake } from "discord.js"; import moment from "moment-timezone"; -import { Constants, TextChannel } from "eris"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { getBaseUrl, sendErrorMessage } from "../../../pluginUtils"; import { canReadChannel } from "../../../utils/canReadChannel"; +import { utilityCmd } from "../types"; export const SourceCmd = utilityCmd({ trigger: "source", @@ -22,9 +21,7 @@ export const SourceCmd = utilityCmd({ return; } - const message = await pluginData.client - .getMessage(args.message.channel.id, args.message.messageId) - .catch(() => null); + const message = await args.message.channel.messages.fetch(args.message.messageId as Snowflake).catch(() => null); if (!message) { sendErrorMessage(pluginData, cmdMessage.channel, "Unknown message"); return; @@ -44,6 +41,6 @@ export const SourceCmd = utilityCmd({ const archiveId = await pluginData.state.archives.create(source, moment.utc().add(1, "hour")); const baseUrl = getBaseUrl(pluginData); const url = pluginData.state.archives.getUrl(baseUrl, archiveId); - cmdMessage.channel.createMessage(`Message source: ${url}`); + cmdMessage.channel.send(`Message source: ${url}`); }, }); diff --git a/backend/src/plugins/Utility/commands/UserInfoCmd.ts b/backend/src/plugins/Utility/commands/UserInfoCmd.ts index dca35e24..c7ce8b48 100644 --- a/backend/src/plugins/Utility/commands/UserInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/UserInfoCmd.ts @@ -1,7 +1,7 @@ -import { utilityCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { getUserInfoEmbed } from "../functions/getUserInfoEmbed"; import { sendErrorMessage } from "../../../pluginUtils"; +import { getUserInfoEmbed } from "../functions/getUserInfoEmbed"; +import { utilityCmd } from "../types"; export const UserInfoCmd = utilityCmd({ trigger: ["user", "userinfo", "whois"], @@ -12,7 +12,7 @@ export const UserInfoCmd = utilityCmd({ signature: { user: ct.resolvedUserLoose({ required: false }), - compact: ct.switchOption({ shortcut: "c" }), + compact: ct.switchOption({ def: false, shortcut: "c" }), }, async run({ message, args, pluginData }) { @@ -23,6 +23,6 @@ export const UserInfoCmd = utilityCmd({ return; } - message.channel.createMessage({ embed }); + message.channel.send({ embeds: [embed] }); }, }); diff --git a/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts b/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts index abd988ab..5bb69961 100644 --- a/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts +++ b/backend/src/plugins/Utility/commands/VcdisconnectCmd.ts @@ -1,16 +1,13 @@ -import { utilityCmd } from "../types"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { VoiceChannel } from "discord.js"; import { - channelMentionRegex, - errorMessage, - isSnowflake, - simpleClosestStringMatch, - stripObjectToScalars, -} from "../../../utils"; -import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { VoiceChannel } from "eris"; + channelToConfigAccessibleChannel, + memberToConfigAccessibleMember, + userToConfigAccessibleUser, +} from "../../../utils/configAccessibleObjects"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; import { LogType } from "../../../data/LogType"; -import { resolveChannel } from "knub/dist/helpers"; +import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { utilityCmd } from "../types"; export const VcdisconnectCmd = utilityCmd({ trigger: ["vcdisconnect", "vcdisc", "vcdc", "vckick", "vck"], @@ -28,31 +25,25 @@ export const VcdisconnectCmd = utilityCmd({ return; } - if (!args.member.voiceState || !args.member.voiceState.channelID) { + if (!args.member.voice?.channelId) { sendErrorMessage(pluginData, msg.channel, "Member is not in a voice channel"); return; } - const channel = (await resolveChannel(pluginData.guild, args.member.voiceState.channelID)) as VoiceChannel; + const channel = pluginData.guild.channels.cache.get(args.member.voice.channelId) as VoiceChannel; try { - await args.member.edit({ - channelID: null, - }); + await args.member.voice.disconnect(); } catch { sendErrorMessage(pluginData, msg.channel, "Failed to disconnect member"); return; } pluginData.state.logs.log(LogType.VOICE_CHANNEL_FORCE_DISCONNECT, { - mod: stripObjectToScalars(msg.author), - member: stripObjectToScalars(args.member, ["user", "roles"]), - oldChannel: stripObjectToScalars(channel), + mod: userToConfigAccessibleUser(msg.author), + member: memberToConfigAccessibleMember(args.member), + oldChannel: channelToConfigAccessibleChannel(channel), }); - sendSuccessMessage( - pluginData, - msg.channel, - `**${args.member.user.username}#${args.member.user.discriminator}** disconnected from **${channel.name}**`, - ); + sendSuccessMessage(pluginData, msg.channel, `**${args.member.user.tag}** disconnected from **${channel.name}**`); }, }); diff --git a/backend/src/plugins/Utility/commands/VcmoveCmd.ts b/backend/src/plugins/Utility/commands/VcmoveCmd.ts index eb85ef71..f2feb8ef 100644 --- a/backend/src/plugins/Utility/commands/VcmoveCmd.ts +++ b/backend/src/plugins/Utility/commands/VcmoveCmd.ts @@ -1,16 +1,15 @@ -import { utilityCmd } from "../types"; -import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { Snowflake, VoiceChannel } from "discord.js"; import { - channelMentionRegex, - errorMessage, - isSnowflake, - resolveMember, - simpleClosestStringMatch, - stripObjectToScalars, -} from "../../../utils"; -import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { Member, VoiceChannel } from "eris"; + channelToConfigAccessibleChannel, + memberToConfigAccessibleMember, + userToConfigAccessibleUser, +} from "../../../utils/configAccessibleObjects"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; import { LogType } from "../../../data/LogType"; +import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { channelMentionRegex, isSnowflake, simpleClosestStringMatch } from "../../../utils"; +import { utilityCmd } from "../types"; +import { ChannelTypeStrings } from "../../../types"; export const VcmoveCmd = utilityCmd({ trigger: "vcmove", @@ -28,7 +27,7 @@ export const VcmoveCmd = utilityCmd({ if (isSnowflake(args.channel)) { // Snowflake -> resolve channel directly - const potentialChannel = pluginData.guild.channels.get(args.channel); + const potentialChannel = pluginData.guild.channels.cache.get(args.channel as Snowflake); if (!potentialChannel || !(potentialChannel instanceof VoiceChannel)) { sendErrorMessage(pluginData, msg.channel, "Unknown or non-voice channel"); return; @@ -38,7 +37,7 @@ export const VcmoveCmd = utilityCmd({ } else if (channelMentionRegex.test(args.channel)) { // Channel mention -> parse channel id and resolve channel from that const channelId = args.channel.match(channelMentionRegex)![1]; - const potentialChannel = pluginData.guild.channels.get(channelId); + const potentialChannel = pluginData.guild.channels.cache.get(channelId as Snowflake); if (!potentialChannel || !(potentialChannel instanceof VoiceChannel)) { sendErrorMessage(pluginData, msg.channel, "Unknown or non-voice channel"); return; @@ -47,9 +46,9 @@ export const VcmoveCmd = utilityCmd({ channel = potentialChannel; } else { // Search string -> find closest matching voice channel name - const voiceChannels = pluginData.guild.channels.filter(theChannel => { - return theChannel instanceof VoiceChannel; - }) as VoiceChannel[]; + const voiceChannels = [...pluginData.guild.channels.cache.values()].filter( + (c): c is VoiceChannel => c.type === ChannelTypeStrings.VOICE, + ); const closestMatch = simpleClosestStringMatch(args.channel, voiceChannels, ch => ch.name); if (!closestMatch) { sendErrorMessage(pluginData, msg.channel, "No matching voice channels"); @@ -59,21 +58,21 @@ export const VcmoveCmd = utilityCmd({ channel = closestMatch; } - if (!args.member.voiceState || !args.member.voiceState.channelID) { + if (!args.member.voice?.channelId) { sendErrorMessage(pluginData, msg.channel, "Member is not in a voice channel"); return; } - if (args.member.voiceState.channelID === channel.id) { + if (args.member.voice.channelId === channel.id) { sendErrorMessage(pluginData, msg.channel, "Member is already on that channel!"); return; } - const oldVoiceChannel = pluginData.guild.channels.get(args.member.voiceState.channelID); + const oldVoiceChannel = pluginData.guild.channels.cache.get(args.member.voice.channelId); try { await args.member.edit({ - channelID: channel.id, + channel: channel.id, }); } catch { sendErrorMessage(pluginData, msg.channel, "Failed to move member"); @@ -81,17 +80,13 @@ export const VcmoveCmd = utilityCmd({ } pluginData.state.logs.log(LogType.VOICE_CHANNEL_FORCE_MOVE, { - mod: stripObjectToScalars(msg.author), - member: stripObjectToScalars(args.member, ["user", "roles"]), - oldChannel: stripObjectToScalars(oldVoiceChannel), - newChannel: stripObjectToScalars(channel), + mod: userToConfigAccessibleUser(msg.author), + member: memberToConfigAccessibleMember(args.member), + oldChannel: channelToConfigAccessibleChannel(oldVoiceChannel!), + newChannel: channelToConfigAccessibleChannel(channel), }); - sendSuccessMessage( - pluginData, - msg.channel, - `**${args.member.user.username}#${args.member.user.discriminator}** moved to **${channel.name}**`, - ); + sendSuccessMessage(pluginData, msg.channel, `**${args.member.user.tag}** moved to **${channel.name}**`); }, }); @@ -111,7 +106,7 @@ export const VcmoveAllCmd = utilityCmd({ if (isSnowflake(args.channel)) { // Snowflake -> resolve channel directly - const potentialChannel = pluginData.guild.channels.get(args.channel); + const potentialChannel = pluginData.guild.channels.cache.get(args.channel as Snowflake); if (!potentialChannel || !(potentialChannel instanceof VoiceChannel)) { sendErrorMessage(pluginData, msg.channel, "Unknown or non-voice channel"); return; @@ -121,7 +116,7 @@ export const VcmoveAllCmd = utilityCmd({ } else if (channelMentionRegex.test(args.channel)) { // Channel mention -> parse channel id and resolve channel from that const channelId = args.channel.match(channelMentionRegex)![1]; - const potentialChannel = pluginData.guild.channels.get(channelId); + const potentialChannel = pluginData.guild.channels.cache.get(channelId as Snowflake); if (!potentialChannel || !(potentialChannel instanceof VoiceChannel)) { sendErrorMessage(pluginData, msg.channel, "Unknown or non-voice channel"); return; @@ -130,9 +125,9 @@ export const VcmoveAllCmd = utilityCmd({ channel = potentialChannel; } else { // Search string -> find closest matching voice channel name - const voiceChannels = pluginData.guild.channels.filter(theChannel => { - return theChannel instanceof VoiceChannel; - }) as VoiceChannel[]; + const voiceChannels = [...pluginData.guild.channels.cache.values()].filter( + (c): c is VoiceChannel => c.type === ChannelTypeStrings.VOICE, + ); const closestMatch = simpleClosestStringMatch(args.channel, voiceChannels, ch => ch.name); if (!closestMatch) { sendErrorMessage(pluginData, msg.channel, "No matching voice channels"); @@ -142,7 +137,7 @@ export const VcmoveAllCmd = utilityCmd({ channel = closestMatch; } - if (args.oldChannel.voiceMembers.size === 0) { + if (args.oldChannel.members.size === 0) { sendErrorMessage(pluginData, msg.channel, "Voice channel is empty"); return; } @@ -154,9 +149,9 @@ export const VcmoveAllCmd = utilityCmd({ // Cant leave null, otherwise we get an assignment error in the catch let currMember = msg.member; - const moveAmt = args.oldChannel.voiceMembers.size; + const moveAmt = args.oldChannel.members.size; let errAmt = 0; - for (const memberWithId of args.oldChannel.voiceMembers) { + for (const memberWithId of args.oldChannel.members) { currMember = memberWithId[1]; // Check for permissions but allow self-moves @@ -164,7 +159,7 @@ export const VcmoveAllCmd = utilityCmd({ sendErrorMessage( pluginData, msg.channel, - `Failed to move ${currMember.username}#${currMember.discriminator} (${currMember.id}): You cannot act on this member`, + `Failed to move ${currMember.user.tag} (${currMember.id}): You cannot act on this member`, ); errAmt++; continue; @@ -172,27 +167,23 @@ export const VcmoveAllCmd = utilityCmd({ try { currMember.edit({ - channelID: channel.id, + channel: channel.id, }); } catch { if (msg.member.id === currMember.id) { sendErrorMessage(pluginData, msg.channel, "Unknown error when trying to move members"); return; } - sendErrorMessage( - pluginData, - msg.channel, - `Failed to move ${currMember.username}#${currMember.discriminator} (${currMember.id})`, - ); + sendErrorMessage(pluginData, msg.channel, `Failed to move ${currMember.user.tag} (${currMember.id})`); errAmt++; continue; } pluginData.state.logs.log(LogType.VOICE_CHANNEL_FORCE_MOVE, { - mod: stripObjectToScalars(msg.author), - member: stripObjectToScalars(currMember, ["user", "roles"]), - oldChannel: stripObjectToScalars(args.oldChannel), - newChannel: stripObjectToScalars(channel), + mod: userToConfigAccessibleUser(msg.author), + member: memberToConfigAccessibleMember(currMember), + oldChannel: channelToConfigAccessibleChannel(args.oldChannel), + newChannel: channelToConfigAccessibleChannel(channel), }); } diff --git a/backend/src/plugins/Utility/events/AutoJoinThreadEvt.ts b/backend/src/plugins/Utility/events/AutoJoinThreadEvt.ts new file mode 100644 index 00000000..7dddcedf --- /dev/null +++ b/backend/src/plugins/Utility/events/AutoJoinThreadEvt.ts @@ -0,0 +1,26 @@ +import { utilityEvt } from "../types"; + +export const AutoJoinThreadEvt = utilityEvt({ + event: "threadCreate", + + async listener(meta) { + const config = meta.pluginData.config.get(); + if (config.autojoin_threads && meta.args.thread.joinable) { + await meta.args.thread.join(); + } + }, +}); + +export const AutoJoinThreadSyncEvt = utilityEvt({ + event: "threadListSync", + + async listener(meta) { + const config = meta.pluginData.config.get(); + if (!config.autojoin_threads) return; + for (const thread of meta.args.threads.values()) { + if (!thread.joined && thread.joinable) { + await thread.join(); + } + } + }, +}); diff --git a/backend/src/plugins/Utility/functions/getChannelInfoEmbed.ts b/backend/src/plugins/Utility/functions/getChannelInfoEmbed.ts index c2f41f74..5052ac11 100644 --- a/backend/src/plugins/Utility/functions/getChannelInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getChannelInfoEmbed.ts @@ -1,10 +1,11 @@ -import { GuildPluginData } from "knub"; -import { UtilityPluginType } from "../types"; -import { Constants, EmbedOptions } from "eris"; -import moment from "moment-timezone"; +import { MessageEmbedOptions, Snowflake, StageChannel, ThreadChannel, VoiceChannel } from "discord.js"; import humanizeDuration from "humanize-duration"; -import { EmbedWith, formatNumber, preEmbedPadding, trimLines } from "../../../utils"; +import { GuildPluginData } from "knub"; +import moment from "moment-timezone"; +import { ChannelTypeStrings } from "src/types"; +import { EmbedWith, formatNumber, MINUTES, preEmbedPadding, trimLines, verboseUserMention } from "../../../utils"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { UtilityPluginType } from "../types"; const TEXT_CHANNEL_ICON = "https://cdn.discordapp.com/attachments/740650744830623756/740656843545772062/text-channel.png"; @@ -14,13 +15,17 @@ const ANNOUNCEMENT_CHANNEL_ICON = "https://cdn.discordapp.com/attachments/740650744830623756/740656841687564348/announcement-channel.png"; const STAGE_CHANNEL_ICON = "https://cdn.discordapp.com/attachments/740650744830623756/839930647711186995/stage-channel.png"; +const PUBLIC_THREAD_ICON = + "https://cdn.discordapp.com/attachments/740650744830623756/870343055855738921/public-thread.png"; +const PRIVATE_THREAD_UCON = + "https://cdn.discordapp.com/attachments/740650744830623756/870343402447839242/private-thread.png"; export async function getChannelInfoEmbed( pluginData: GuildPluginData, channelId: string, requestMemberId?: string, -): Promise { - const channel = pluginData.guild.channels.get(channelId); +): Promise { + const channel = pluginData.guild.channels.cache.get(channelId as Snowflake); if (!channel) { return null; } @@ -29,23 +34,26 @@ export async function getChannelInfoEmbed( fields: [], }; - let icon = TEXT_CHANNEL_ICON; - if (channel.type === Constants.ChannelTypes.GUILD_VOICE) { - icon = VOICE_CHANNEL_ICON; - } else if (channel.type === Constants.ChannelTypes.GUILD_NEWS) { - icon = ANNOUNCEMENT_CHANNEL_ICON; - } else if (channel.type === Constants.ChannelTypes.GUILD_STAGE) { - icon = STAGE_CHANNEL_ICON; - } + const icon = + { + [ChannelTypeStrings.VOICE]: VOICE_CHANNEL_ICON, + [ChannelTypeStrings.NEWS]: ANNOUNCEMENT_CHANNEL_ICON, + [ChannelTypeStrings.STAGE]: STAGE_CHANNEL_ICON, + [ChannelTypeStrings.PUBLIC_THREAD]: PUBLIC_THREAD_ICON, + [ChannelTypeStrings.PRIVATE_THREAD]: PRIVATE_THREAD_UCON, + }[channel.type] || TEXT_CHANNEL_ICON; const channelType = { - [Constants.ChannelTypes.GUILD_TEXT]: "Text channel", - [Constants.ChannelTypes.GUILD_VOICE]: "Voice channel", - [Constants.ChannelTypes.GUILD_CATEGORY]: "Category", - [Constants.ChannelTypes.GUILD_NEWS]: "Announcement channel", - [Constants.ChannelTypes.GUILD_STORE]: "Store channel", - [Constants.ChannelTypes.GUILD_STAGE]: "Stage channel", + [ChannelTypeStrings.TEXT]: "Text channel", + [ChannelTypeStrings.VOICE]: "Voice channel", + [ChannelTypeStrings.CATEGORY]: "Category", + [ChannelTypeStrings.NEWS]: "Announcement channel", + [ChannelTypeStrings.STORE]: "Store channel", + [ChannelTypeStrings.STAGE]: "Stage channel", + [ChannelTypeStrings.PUBLIC_THREAD]: "Public Thread channel", + [ChannelTypeStrings.PRIVATE_THREAD]: "Private Thread channel", + [ChannelTypeStrings.NEWS_THREAD]: "News Thread channel", }[channel.type] || "Channel"; embed.author = { @@ -55,9 +63,9 @@ export async function getChannelInfoEmbed( let channelName = `#${channel.name}`; if ( - channel.type === Constants.ChannelTypes.GUILD_VOICE || - channel.type === Constants.ChannelTypes.GUILD_CATEGORY || - channel.type === Constants.ChannelTypes.GUILD_STAGE + channel.type === ChannelTypeStrings.VOICE || + channel.type === ChannelTypeStrings.CATEGORY || + channel.type === ChannelTypeStrings.STAGE ) { channelName = channel.name; } @@ -68,12 +76,12 @@ export async function getChannelInfoEmbed( ? await timeAndDate.inMemberTz(requestMemberId, createdAt) : timeAndDate.inGuildTz(createdAt); const prettyCreatedAt = tzCreatedAt.format(timeAndDate.getDateFormat("pretty_datetime")); - const channelAge = humanizeDuration(Date.now() - channel.createdAt, { + const channelAge = humanizeDuration(Date.now() - channel.createdTimestamp, { largest: 2, round: true, }); - const showMention = channel.type !== Constants.ChannelTypes.GUILD_CATEGORY; + const showMention = channel.type !== ChannelTypeStrings.CATEGORY; embed.fields.push({ name: preEmbedPadding + "Channel information", @@ -86,11 +94,11 @@ export async function getChannelInfoEmbed( `), }); - if (channel.type === Constants.ChannelTypes.GUILD_VOICE || channel.type === Constants.ChannelTypes.GUILD_STAGE) { - const voiceMembers = Array.from(channel.voiceMembers.values()); - const muted = voiceMembers.filter(vm => vm.voiceState.mute || vm.voiceState.selfMute); - const deafened = voiceMembers.filter(vm => vm.voiceState.deaf || vm.voiceState.selfDeaf); - const voiceOrStage = channel.type === Constants.ChannelTypes.GUILD_VOICE ? "Voice" : "Stage"; + if (channel.type === ChannelTypeStrings.VOICE || channel.type === ChannelTypeStrings.STAGE) { + const voiceMembers = Array.from((channel as VoiceChannel | StageChannel).members.values()); + const muted = voiceMembers.filter(vm => vm.voice.mute || vm.voice.selfMute); + const deafened = voiceMembers.filter(vm => vm.voice.deaf || vm.voice.selfDeaf); + const voiceOrStage = channel.type === ChannelTypeStrings.VOICE ? "Voice" : "Stage"; embed.fields.push({ name: preEmbedPadding + `${voiceOrStage} information`, @@ -102,24 +110,42 @@ export async function getChannelInfoEmbed( }); } - if (channel.type === Constants.ChannelTypes.GUILD_CATEGORY) { - const textChannels = pluginData.guild.channels.filter( - ch => ch.parentID === channel.id && ch.type !== Constants.ChannelTypes.GUILD_VOICE, + if (channel.type === ChannelTypeStrings.CATEGORY) { + const textChannels = pluginData.guild.channels.cache.filter( + ch => ch.parentId === channel.id && ch.type !== ChannelTypeStrings.VOICE, ); - const voiceChannels = pluginData.guild.channels.filter( + const voiceChannels = pluginData.guild.channels.cache.filter( ch => - ch.parentID === channel.id && - (ch.type === Constants.ChannelTypes.GUILD_VOICE || ch.type === Constants.ChannelTypes.GUILD_STAGE), + ch.parentId === channel.id && (ch.type === ChannelTypeStrings.VOICE || ch.type === ChannelTypeStrings.STAGE), ); embed.fields.push({ name: preEmbedPadding + "Category information", value: trimLines(` - Text channels: **${textChannels.length}** - Voice channels: **${voiceChannels.length}** + Text channels: **${textChannels.size}** + Voice channels: **${voiceChannels.size}** `), }); } + if (channel.type === ChannelTypeStrings.PRIVATE_THREAD || channel.type === ChannelTypeStrings.PUBLIC_THREAD) { + const thread = channel as ThreadChannel; + const parentChannelName = thread.parent?.name ?? `<#${thread.parentId}>`; + const memberCount = thread.memberCount ?? thread.members.cache.size; + const owner = await thread.fetchOwner().catch(() => null); + const ownerMention = owner?.user ? verboseUserMention(owner.user) : "Unknown#0000"; + const autoArchiveDuration = thread.autoArchiveDuration === "MAX" ? 10080 : thread.autoArchiveDuration; // TODO: Boost level check + const humanizedArchiveTime = `Archive duration: **${humanizeDuration((autoArchiveDuration ?? 0) * MINUTES)}**`; + + embed.fields.push({ + name: preEmbedPadding + "Thread information", + value: trimLines(` + Parent channel: **#${parentChannelName}** + Member count: **${memberCount}** + Thread creator: ${ownerMention} + ${thread.archived ? "Archived: **True**" : humanizedArchiveTime}`), + }); + } + return embed; } diff --git a/backend/src/plugins/Utility/functions/getEmojiInfoEmbed.ts b/backend/src/plugins/Utility/functions/getEmojiInfoEmbed.ts index 28ee5712..02228924 100644 --- a/backend/src/plugins/Utility/functions/getEmojiInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getEmojiInfoEmbed.ts @@ -1,13 +1,13 @@ -import { EmbedOptions } from "eris"; +import { MessageEmbedOptions } from "discord.js"; import { GuildPluginData } from "knub"; +import { EmbedWith, preEmbedPadding, trimLines } from "../../../utils"; import { UtilityPluginType } from "../types"; -import { trimLines, preEmbedPadding, EmbedWith } from "../../../utils"; export async function getEmojiInfoEmbed( pluginData: GuildPluginData, emojiId: string, -): Promise { - const emoji = pluginData.guild.emojis.find(e => e.id === emojiId); +): Promise { + const emoji = pluginData.guild.emojis.cache.find(e => e.id === emojiId); if (!emoji) { return null; } diff --git a/backend/src/plugins/Utility/functions/getGuildPreview.ts b/backend/src/plugins/Utility/functions/getGuildPreview.ts index a72be048..fc1d00db 100644 --- a/backend/src/plugins/Utility/functions/getGuildPreview.ts +++ b/backend/src/plugins/Utility/functions/getGuildPreview.ts @@ -1,9 +1,13 @@ -import { Client, GuildPreview } from "eris"; +import { Client, GuildPreview, Snowflake } from "discord.js"; import { memoize, MINUTES } from "../../../utils"; /** * Memoized getGuildPreview */ export function getGuildPreview(client: Client, guildId: string): Promise { - return memoize(() => client.getGuildPreview(guildId).catch(() => null), `getGuildPreview_${guildId}`, 10 * MINUTES); + return memoize( + () => client.fetchGuildPreview(guildId as Snowflake).catch(() => null), + `getGuildPreview_${guildId}`, + 10 * MINUTES, + ); } diff --git a/backend/src/plugins/Utility/functions/getInviteInfoEmbed.ts b/backend/src/plugins/Utility/functions/getInviteInfoEmbed.ts index 84760b1f..a95de14b 100644 --- a/backend/src/plugins/Utility/functions/getInviteInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getInviteInfoEmbed.ts @@ -1,14 +1,12 @@ -import { GuildPluginData } from "knub"; -import { UtilityPluginType } from "../types"; -import { Constants, EmbedOptions } from "eris"; -import { snowflakeToTimestamp } from "../../../utils/snowflakeToTimestamp"; -import moment from "moment-timezone"; +import { MessageEmbedOptions } from "discord.js"; import humanizeDuration from "humanize-duration"; +import { GuildPluginData } from "knub"; +import moment from "moment-timezone"; +import { ChannelTypeStrings } from "src/types"; import { - embedPadding, EmbedWith, - emptyEmbedValue, formatNumber, + GroupDMInvite, inviteHasCounts, isGroupDMInvite, isGuildInvite, @@ -16,12 +14,14 @@ import { resolveInvite, trimLines, } from "../../../utils"; +import { snowflakeToTimestamp } from "../../../utils/snowflakeToTimestamp"; +import { UtilityPluginType } from "../types"; export async function getInviteInfoEmbed( pluginData: GuildPluginData, inviteCode: string, -): Promise { - const invite = await resolveInvite(pluginData.client, inviteCode, true); +): Promise { + let invite = await resolveInvite(pluginData.client, inviteCode, true); if (!invite) { return null; } @@ -67,9 +67,7 @@ export async function getInviteInfoEmbed( }); const channelName = - invite.channel.type === Constants.ChannelTypes.GUILD_VOICE - ? `🔉 ${invite.channel.name}` - : `#${invite.channel.name}`; + invite.channel.type === ChannelTypeStrings.VOICE ? `🔉 ${invite.channel.name}` : `#${invite.channel.name}`; const channelCreatedAtTimestamp = snowflakeToTimestamp(invite.channel.id); const channelCreatedAt = moment.utc(channelCreatedAtTimestamp, "x"); @@ -84,7 +82,7 @@ export async function getInviteInfoEmbed( Created: **${channelAge} ago** `); - if (invite.channel.type !== Constants.ChannelTypes.GUILD_VOICE) { + if (invite.channel.type !== ChannelTypeStrings.VOICE) { channelInfo += `\nMention: <#${invite.channel.id}>`; } @@ -98,7 +96,7 @@ export async function getInviteInfoEmbed( embed.fields.push({ name: preEmbedPadding + "Invite creator", value: trimLines(` - Name: **${invite.inviter.username}#${invite.inviter.discriminator}** + Name: **${invite.inviter.tag}** ID: \`${invite.inviter.id}\` Mention: <@!${invite.inviter.id}> `), @@ -113,16 +111,17 @@ export async function getInviteInfoEmbed( fields: [], }; + invite = invite as GroupDMInvite; embed.author = { name: invite.channel.name ? `Group DM invite: ${invite.channel.name}` : `Group DM invite`, url: `https://discord.gg/${invite.code}`, - }; + }; // FIXME pending invite re-think - if (invite.channel.icon) { + /*if (invite.channel.icon) { embed.author.icon_url = `https://cdn.discordapp.com/channel-icons/${invite.channel.id}/${invite.channel.icon}.png?size=256`; - } - - const channelCreatedAtTimestamp = snowflakeToTimestamp(invite.channel.id); + }*/ const channelCreatedAtTimestamp = snowflakeToTimestamp( + invite.channel.id, + ); const channelCreatedAt = moment.utc(channelCreatedAtTimestamp, "x"); const channelAge = humanizeDuration(Date.now() - channelCreatedAtTimestamp, { largest: 2, @@ -144,7 +143,7 @@ export async function getInviteInfoEmbed( embed.fields.push({ name: preEmbedPadding + "Invite creator", value: trimLines(` - Name: **${invite.inviter.username}#${invite.inviter.discriminator}** + Name: **${invite.inviter.tag}** ID: \`${invite.inviter.id}\` Mention: <@!${invite.inviter.id}> `), diff --git a/backend/src/plugins/Utility/functions/getMessageInfoEmbed.ts b/backend/src/plugins/Utility/functions/getMessageInfoEmbed.ts index 3c74fc3a..17e2c63f 100644 --- a/backend/src/plugins/Utility/functions/getMessageInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getMessageInfoEmbed.ts @@ -1,11 +1,12 @@ -import { GuildPluginData } from "knub"; -import { UtilityPluginType } from "../types"; -import { Constants, EmbedOptions } from "eris"; -import moment from "moment-timezone"; +import { MessageEmbedOptions, Snowflake, TextChannel } from "discord.js"; import humanizeDuration from "humanize-duration"; -import { chunkMessageLines, EmbedWith, messageLink, preEmbedPadding, trimEmptyLines, trimLines } from "../../../utils"; +import { GuildPluginData } from "knub"; import { getDefaultPrefix } from "knub/dist/commands/commandUtils"; +import moment from "moment-timezone"; +import { MessageTypeStrings } from "src/types"; +import { chunkMessageLines, EmbedWith, messageLink, preEmbedPadding, trimEmptyLines, trimLines } from "../../../utils"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { UtilityPluginType } from "../types"; const MESSAGE_ICON = "https://cdn.discordapp.com/attachments/740650744830623756/740685652152025088/message.png"; @@ -14,8 +15,10 @@ export async function getMessageInfoEmbed( channelId: string, messageId: string, requestMemberId?: string, -): Promise { - const message = await pluginData.client.getMessage(channelId, messageId).catch(() => null); +): Promise { + const message = await (pluginData.guild.channels.resolve(channelId as Snowflake) as TextChannel).messages + .fetch(messageId as Snowflake) + .catch(() => null); if (!message) { return null; } @@ -36,12 +39,12 @@ export async function getMessageInfoEmbed( ? await timeAndDate.inMemberTz(requestMemberId, createdAt) : timeAndDate.inGuildTz(createdAt); const prettyCreatedAt = tzCreatedAt.format(timeAndDate.getDateFormat("pretty_datetime")); - const messageAge = humanizeDuration(Date.now() - message.createdAt, { + const messageAge = humanizeDuration(Date.now() - message.createdTimestamp, { largest: 2, round: true, }); - const editedAt = message.editedTimestamp && moment.utc(message.editedTimestamp, "x"); + const editedAt = message.editedTimestamp ? moment.utc(message.editedTimestamp!, "x") : undefined; const tzEditedAt = requestMemberId ? await timeAndDate.inMemberTz(requestMemberId, editedAt) : timeAndDate.inGuildTz(editedAt); @@ -55,16 +58,16 @@ export async function getMessageInfoEmbed( const type = { - [Constants.MessageTypes.DEFAULT]: "Regular message", - [Constants.MessageTypes.CHANNEL_PINNED_MESSAGE]: "System message", - [Constants.MessageTypes.GUILD_MEMBER_JOIN]: "System message", - [Constants.MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION]: "System message", - [Constants.MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1]: "System message", - [Constants.MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2]: "System message", - [Constants.MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3]: "System message", - [Constants.MessageTypes.CHANNEL_FOLLOW_ADD]: "System message", - [Constants.MessageTypes.GUILD_DISCOVERY_DISQUALIFIED]: "System message", - [Constants.MessageTypes.GUILD_DISCOVERY_REQUALIFIED]: "System message", + [MessageTypeStrings.DEFAULT]: "Regular message", + [MessageTypeStrings.PINS_ADD]: "System message", + [MessageTypeStrings.GUILD_MEMBER_JOIN]: "System message", + [MessageTypeStrings.USER_PREMIUM_GUILD_SUBSCRIPTION]: "System message", + [MessageTypeStrings.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1]: "System message", + [MessageTypeStrings.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2]: "System message", + [MessageTypeStrings.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3]: "System message", + [MessageTypeStrings.CHANNEL_FOLLOW_ADD]: "System message", + [MessageTypeStrings.GUILD_DISCOVERY_DISQUALIFIED]: "System message", + [MessageTypeStrings.GUILD_DISCOVERY_REQUALIFIED]: "System message", }[message.type] || "Unknown"; embed.fields.push({ @@ -87,12 +90,12 @@ export async function getMessageInfoEmbed( ? await timeAndDate.inMemberTz(requestMemberId, authorCreatedAt) : timeAndDate.inGuildTz(authorCreatedAt); const prettyAuthorCreatedAt = tzAuthorCreatedAt.format(timeAndDate.getDateFormat("pretty_datetime")); - const authorAccountAge = humanizeDuration(Date.now() - message.author.createdAt, { + const authorAccountAge = humanizeDuration(Date.now() - message.author.createdTimestamp, { largest: 2, round: true, }); - const authorJoinedAt = message.member && moment.utc(message.member.joinedAt, "x"); + const authorJoinedAt = message.member && moment.utc(message.member.joinedTimestamp!, "x"); const tzAuthorJoinedAt = authorJoinedAt ? requestMemberId ? await timeAndDate.inMemberTz(requestMemberId, authorJoinedAt) @@ -101,7 +104,7 @@ export async function getMessageInfoEmbed( const prettyAuthorJoinedAt = tzAuthorJoinedAt?.format(timeAndDate.getDateFormat("pretty_datetime")); const authorServerAge = message.member && - humanizeDuration(Date.now() - message.member.joinedAt, { + humanizeDuration(Date.now() - message.member.joinedTimestamp!, { largest: 2, round: true, }); @@ -109,7 +112,7 @@ export async function getMessageInfoEmbed( embed.fields.push({ name: preEmbedPadding + "Author information", value: trimLines(` - Name: **${message.author.username}#${message.author.discriminator}** + Name: **${message.author.tag}** ID: \`${message.author.id}\` Created: **${authorAccountAge} ago** (\`${prettyAuthorCreatedAt}\`) ${authorJoinedAt ? `Joined: **${authorServerAge} ago** (\`${prettyAuthorJoinedAt}\`)` : ""} @@ -126,7 +129,7 @@ export async function getMessageInfoEmbed( }); } - if (message.attachments.length) { + if (message.attachments.size) { embed.fields.push({ name: preEmbedPadding + "Attachments", value: message.attachments[0].url, diff --git a/backend/src/plugins/Utility/functions/getRoleInfoEmbed.ts b/backend/src/plugins/Utility/functions/getRoleInfoEmbed.ts index 5dcfcb26..4f16c31a 100644 --- a/backend/src/plugins/Utility/functions/getRoleInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getRoleInfoEmbed.ts @@ -1,10 +1,10 @@ -import { EmbedOptions, Role } from "eris"; -import { GuildPluginData } from "knub"; -import { UtilityPluginType } from "../types"; -import { trimLines, preEmbedPadding, EmbedWith } from "../../../utils"; -import moment from "moment-timezone"; +import { MessageEmbedOptions, Role } from "discord.js"; import humanizeDuration from "humanize-duration"; +import { GuildPluginData } from "knub"; +import moment from "moment-timezone"; +import { EmbedWith, preEmbedPadding, trimLines } from "../../../utils"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { UtilityPluginType } from "../types"; const MENTION_ICON = "https://cdn.discordapp.com/attachments/705009450855039042/839284872152481792/mention.png"; @@ -12,7 +12,7 @@ export async function getRoleInfoEmbed( pluginData: GuildPluginData, role: Role, requestMemberId?: string, -): Promise { +): Promise { const embed: EmbedWith<"fields"> = { fields: [], }; @@ -30,12 +30,12 @@ export async function getRoleInfoEmbed( ? await timeAndDate.inMemberTz(requestMemberId, createdAt) : timeAndDate.inGuildTz(createdAt); const prettyCreatedAt = tzCreatedAt.format(timeAndDate.getDateFormat("pretty_datetime")); - const roleAge = humanizeDuration(Date.now() - role.createdAt, { + const roleAge = humanizeDuration(Date.now() - role.createdTimestamp, { largest: 2, round: true, }); - const rolePerms = Object.keys(role.permissions.json).map(p => + const rolePerms = Object.keys(role.permissions.toJSON()).map(p => p // Voice channel related permission names start with 'voice' .replace(/^voice/i, "") @@ -45,7 +45,7 @@ export async function getRoleInfoEmbed( ); // -1 because of the @everyone role - const totalGuildRoles = pluginData.guild.roles.size - 1; + const totalGuildRoles = pluginData.guild.roles.cache.size - 1; embed.fields.push({ name: preEmbedPadding + "Role information", diff --git a/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts b/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts index 0a7564cc..a0922399 100644 --- a/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts @@ -1,7 +1,9 @@ +import { MessageEmbedOptions, Snowflake } from "discord.js"; +import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; -import { UtilityPluginType } from "../types"; +import moment from "moment-timezone"; +import { ChannelTypeStrings } from "../../../types"; import { - embedPadding, EmbedWith, formatNumber, inviteHasCounts, @@ -12,21 +14,20 @@ import { resolveUser, trimLines, } from "../../../utils"; -import { CategoryChannel, EmbedOptions, Guild, TextChannel, VoiceChannel } from "eris"; -import moment from "moment-timezone"; -import humanizeDuration from "humanize-duration"; -import { getGuildPreview } from "./getGuildPreview"; +import { idToTimestamp } from "../../../utils/idToTimestamp"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { UtilityPluginType } from "../types"; +import { getGuildPreview } from "./getGuildPreview"; export async function getServerInfoEmbed( pluginData: GuildPluginData, serverId: string, requestMemberId?: string, -): Promise { +): Promise { const thisServer = serverId === pluginData.guild.id ? pluginData.guild : null; const [restGuild, guildPreview] = await Promise.all([ thisServer - ? memoize(() => pluginData.client.getRESTGuild(serverId), `getRESTGuild_${serverId}`, 10 * MINUTES) + ? memoize(() => pluginData.client.guilds.fetch(serverId as Snowflake), `getRESTGuild_${serverId}`, 10 * MINUTES) : null, getGuildPreview(pluginData.client, serverId), ]); @@ -46,12 +47,12 @@ export async function getServerInfoEmbed( embed.author = { name: `Server: ${(guildPreview || restGuild)!.name}`, - icon_url: (guildPreview || restGuild)!.iconURL ?? undefined, + iconURL: (guildPreview || restGuild)!.iconURL() ?? undefined, }; // BASIC INFORMATION const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin); - const createdAt = moment.utc((guildPreview || restGuild)!.createdAt, "x"); + const createdAt = moment.utc(idToTimestamp((guildPreview || restGuild)!.id)!, "x"); const tzCreatedAt = requestMemberId ? await timeAndDate.inMemberTz(requestMemberId, createdAt) : timeAndDate.inGuildTz(createdAt); @@ -65,11 +66,11 @@ export async function getServerInfoEmbed( basicInformation.push(`Created: **${serverAge} ago** (\`${prettyCreatedAt}\`)`); if (thisServer) { - const owner = await resolveUser(pluginData.client, thisServer.ownerID); - const ownerName = `${owner.username}#${owner.discriminator}`; + const owner = await resolveUser(pluginData.client, thisServer.ownerId); + const ownerName = owner.tag; - basicInformation.push(`Owner: **${ownerName}** (\`${thisServer.ownerID}\`)`); - basicInformation.push(`Voice region: **${thisServer.region}**`); + basicInformation.push(`Owner: **${ownerName}** (\`${thisServer.ownerId}\`)`); + // basicInformation.push(`Voice region: **${thisServer.region}**`); Outdated, as automatic voice regions are fully live } if (features.length > 0) { @@ -82,12 +83,11 @@ export async function getServerInfoEmbed( }); // IMAGE LINKS - const iconUrl = `[Link](${(restGuild || guildPreview)!.iconURL})`; - const bannerUrl = restGuild?.bannerURL ? `[Link](${restGuild.bannerURL})` : "None"; - const splashUrl = - (restGuild || guildPreview)!.splashURL != null - ? `[Link](${(restGuild || guildPreview)!.splashURL?.replace("size=128", "size=2048")})` - : "None"; + const iconUrl = `[Link](${(restGuild || guildPreview)!.iconURL({ dynamic: true, format: "png", size: 2048 })})`; + const bannerUrl = restGuild?.banner ? `[Link](${restGuild.bannerURL({ format: "png", size: 2048 })})` : "None"; + const splashUrl = (restGuild || guildPreview)!.splash + ? `[Link](${(restGuild || guildPreview)!.splashURL({ format: "png", size: 2048 })})` + : "None"; embed.fields.push( { @@ -113,33 +113,33 @@ export async function getServerInfoEmbed( restGuild?.approximateMemberCount || restGuild?.memberCount || thisServer?.memberCount || - thisServer?.members.size || + thisServer?.members.cache.size || 0; let onlineMemberCount = (guildPreview?.approximatePresenceCount || restGuild?.approximatePresenceCount)!; - if (onlineMemberCount == null && restGuild?.vanityURL) { + if (onlineMemberCount == null && restGuild?.vanityURLCode) { // For servers with a vanity URL, we can also use the numbers from the invite for online count - const invite = await resolveInvite(pluginData.client, restGuild.vanityURL!, true); + const invite = await resolveInvite(pluginData.client, restGuild.vanityURLCode!, true); if (invite && inviteHasCounts(invite)) { onlineMemberCount = invite.presenceCount; } } if (!onlineMemberCount && thisServer) { - onlineMemberCount = thisServer.members.filter(m => m.status !== "offline").length; // Extremely inaccurate fallback + onlineMemberCount = thisServer.members.cache.filter(m => m.presence?.status !== "offline").size; // Extremely inaccurate fallback } const offlineMemberCount = totalMembers - onlineMemberCount; let memberCountTotalLines = `Total: **${formatNumber(totalMembers)}**`; - if (restGuild?.maxMembers) { - memberCountTotalLines += `\nMax: **${formatNumber(restGuild.maxMembers)}**`; + if (restGuild?.maximumMembers) { + memberCountTotalLines += `\nMax: **${formatNumber(restGuild.maximumMembers)}**`; } let memberCountOnlineLines = `Online: **${formatNumber(onlineMemberCount)}**`; - if (restGuild?.maxPresences) { - memberCountOnlineLines += `\nMax online: **${formatNumber(restGuild.maxPresences)}**`; + if (restGuild?.maximumPresences) { + memberCountOnlineLines += `\nMax online: **${formatNumber(restGuild.maximumPresences)}**`; } embed.fields.push({ @@ -154,19 +154,19 @@ export async function getServerInfoEmbed( // CHANNEL COUNTS if (thisServer) { - const totalChannels = thisServer.channels.size; - const categories = thisServer.channels.filter(channel => channel instanceof CategoryChannel); - const textChannels = thisServer.channels.filter(channel => channel instanceof TextChannel); - const voiceChannels = thisServer.channels.filter(channel => channel instanceof VoiceChannel); + const totalChannels = thisServer.channels.cache.size; + const categories = thisServer.channels.cache.filter(channel => channel.type === ChannelTypeStrings.CATEGORY); + const textChannels = thisServer.channels.cache.filter(channel => channel.type === ChannelTypeStrings.TEXT); + const voiceChannels = thisServer.channels.cache.filter(channel => channel.type === ChannelTypeStrings.VOICE); embed.fields.push({ name: preEmbedPadding + "Channels", inline: true, value: trimLines(` Total: **${totalChannels}** / 500 - Categories: **${categories.length}** - Text: **${textChannels.length}** - Voice: **${voiceChannels.length}** + Categories: **${categories.size}** + Text: **${textChannels.size}** + Voice: **${voiceChannels.size}** `), }); } @@ -175,7 +175,7 @@ export async function getServerInfoEmbed( const otherStats: string[] = []; if (thisServer) { - otherStats.push(`Roles: **${thisServer.roles.size}** / 250`); + otherStats.push(`Roles: **${thisServer.roles.cache.size}** / 250`); } if (restGuild) { @@ -186,9 +186,19 @@ export async function getServerInfoEmbed( 2: 150, 3: 250, }[restGuild.premiumTier] || 50; - otherStats.push(`Emojis: **${restGuild.emojis.length}** / ${maxEmojis * 2}`); + const maxStickers = + { + 0: 0, + 1: 15, + 2: 30, + 3: 60, + }[restGuild.premiumTier] || 0; + + otherStats.push(`Emojis: **${restGuild.emojis.cache.size}** / ${maxEmojis * 2}`); + otherStats.push(`Stickers: **${restGuild.stickers.cache.size}** / ${maxStickers}`); } else { - otherStats.push(`Emojis: **${guildPreview!.emojis.length}**`); + otherStats.push(`Emojis: **${guildPreview!.emojis.size}**`); + // otherStats.push(`Stickers: **${guildPreview!.stickers.size}**`); Wait on DJS } if (thisServer) { diff --git a/backend/src/plugins/Utility/functions/getSnowflakeInfoEmbed.ts b/backend/src/plugins/Utility/functions/getSnowflakeInfoEmbed.ts index 5a9e9dc4..3ef1777f 100644 --- a/backend/src/plugins/Utility/functions/getSnowflakeInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getSnowflakeInfoEmbed.ts @@ -1,20 +1,11 @@ -import { Message, GuildTextableChannel, EmbedOptions } from "eris"; -import { GuildPluginData } from "knub"; -import { UtilityPluginType } from "../types"; -import { - UnknownUser, - trimLines, - embedPadding, - resolveMember, - resolveUser, - preEmbedPadding, - EmbedWith, -} from "../../../utils"; -import moment from "moment-timezone"; -import { CaseTypes } from "../../../data/CaseTypes"; +import { MessageEmbedOptions } from "discord.js"; import humanizeDuration from "humanize-duration"; +import { GuildPluginData } from "knub"; +import moment from "moment-timezone"; +import { EmbedWith, preEmbedPadding } from "../../../utils"; import { snowflakeToTimestamp } from "../../../utils/snowflakeToTimestamp"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { UtilityPluginType } from "../types"; const SNOWFLAKE_ICON = "https://cdn.discordapp.com/attachments/740650744830623756/742020790471491668/snowflake.png"; @@ -23,7 +14,7 @@ export async function getSnowflakeInfoEmbed( snowflake: string, showUnknownWarning = false, requestMemberId?: string, -): Promise { +): Promise { const embed: EmbedWith<"fields"> = { fields: [], }; diff --git a/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts b/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts index c3acdda3..78970a0e 100644 --- a/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts @@ -1,28 +1,27 @@ -import { Message, GuildTextableChannel, EmbedOptions, Role } from "eris"; +import { MessageEmbedOptions, Role } from "discord.js"; +import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; -import { UtilityPluginType } from "../types"; -import { - UnknownUser, - trimLines, - embedPadding, - resolveMember, - resolveUser, - preEmbedPadding, - sorter, - messageLink, - EmbedWith, -} from "../../../utils"; import moment from "moment-timezone"; import { CaseTypes } from "../../../data/CaseTypes"; -import humanizeDuration from "humanize-duration"; +import { + EmbedWith, + messageLink, + preEmbedPadding, + resolveMember, + resolveUser, + sorter, + trimLines, + UnknownUser, +} from "../../../utils"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; +import { UtilityPluginType } from "../types"; export async function getUserInfoEmbed( pluginData: GuildPluginData, userId: string, compact = false, requestMemberId?: string, -): Promise { +): Promise { const user = await resolveUser(pluginData.client, userId); if (!user || user instanceof UnknownUser) { return null; @@ -37,10 +36,10 @@ export async function getUserInfoEmbed( const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin); embed.author = { - name: `User: ${user.username}#${user.discriminator}`, + name: `User: ${user.tag}`, }; - const avatarURL = user.avatarURL || user.defaultAvatarURL; + const avatarURL = user.displayAvatarURL(); embed.author.icon_url = avatarURL; const createdAt = moment.utc(user.createdAt, "x"); @@ -48,7 +47,7 @@ export async function getUserInfoEmbed( ? await timeAndDate.inMemberTz(requestMemberId, createdAt) : timeAndDate.inGuildTz(createdAt); const prettyCreatedAt = tzCreatedAt.format(timeAndDate.getDateFormat("pretty_datetime")); - const accountAge = humanizeDuration(moment.utc().valueOf() - user.createdAt, { + const accountAge = humanizeDuration(moment.utc().valueOf() - user.createdTimestamp, { largest: 2, round: true, }); @@ -62,12 +61,12 @@ export async function getUserInfoEmbed( `), }); if (member) { - const joinedAt = moment.utc(member.joinedAt, "x"); + const joinedAt = moment.utc(member.joinedTimestamp!, "x"); const tzJoinedAt = requestMemberId ? await timeAndDate.inMemberTz(requestMemberId, joinedAt) : timeAndDate.inGuildTz(joinedAt); const prettyJoinedAt = tzJoinedAt.format(timeAndDate.getDateFormat("pretty_datetime")); - const joinAge = humanizeDuration(moment.utc().valueOf() - member.joinedAt, { + const joinAge = humanizeDuration(moment.utc().valueOf() - member.joinedTimestamp!, { largest: 2, round: true, }); @@ -85,7 +84,7 @@ export async function getUserInfoEmbed( embed.fields.push({ name: preEmbedPadding + "User information", value: trimLines(` - Name: **${user.username}#${user.discriminator}** + Name: **${user.tag}** ID: \`${user.id}\` Created: **${accountAge} ago** (\`${prettyCreatedAt}\`) Mention: <@!${user.id}> @@ -93,16 +92,18 @@ export async function getUserInfoEmbed( }); if (member) { - const joinedAt = moment.utc(member.joinedAt, "x"); + const joinedAt = moment.utc(member.joinedTimestamp!, "x"); const tzJoinedAt = requestMemberId ? await timeAndDate.inMemberTz(requestMemberId, joinedAt) : timeAndDate.inGuildTz(joinedAt); const prettyJoinedAt = tzJoinedAt.format(timeAndDate.getDateFormat("pretty_datetime")); - const joinAge = humanizeDuration(moment.utc().valueOf() - member.joinedAt, { + const joinAge = humanizeDuration(moment.utc().valueOf() - member.joinedTimestamp!, { largest: 2, round: true, }); - const roles = member.roles.map(id => pluginData.guild.roles.get(id)).filter(r => r != null) as Role[]; + const roles = member.roles.cache + .map(role => pluginData.guild.roles.cache.get(role.id)) + .filter((r): r is Role => !!r); roles.sort(sorter("position", "DESC")); embed.fields.push({ @@ -113,16 +114,14 @@ export async function getUserInfoEmbed( `), }); - const voiceChannel = member.voiceState.channelID - ? pluginData.guild.channels.get(member.voiceState.channelID) - : null; - if (voiceChannel || member.voiceState.mute || member.voiceState.deaf) { + const voiceChannel = member.voice.channelId ? pluginData.guild.channels.cache.get(member.voice.channelId) : null; + if (voiceChannel || member.voice.mute || member.voice.deaf) { embed.fields.push({ name: preEmbedPadding + "Voice information", value: trimLines(` - ${voiceChannel ? `Current voice channel: **${voiceChannel ? voiceChannel.name : "None"}**` : ""} - ${member.voiceState.mute ? "Server voice muted: **Yes**" : ""} - ${member.voiceState.deaf ? "Server voice deafened: **Yes**" : ""} + ${voiceChannel ? `Current voice channel: **${voiceChannel.name ?? "None"}**` : ""} + ${member.voice.mute ? "Server voice muted: **Yes**" : ""} + ${member.voice.deaf ? "Server voice deafened: **Yes**" : ""} `), }); } diff --git a/backend/src/plugins/Utility/guildReloads.ts b/backend/src/plugins/Utility/guildReloads.ts index 3de20bb8..b5cb292b 100644 --- a/backend/src/plugins/Utility/guildReloads.ts +++ b/backend/src/plugins/Utility/guildReloads.ts @@ -1,3 +1,3 @@ -import { TextChannel } from "eris"; +import { TextChannel } from "discord.js"; export const activeReloads: Map = new Map(); diff --git a/backend/src/plugins/Utility/refreshMembers.ts b/backend/src/plugins/Utility/refreshMembers.ts index 50803e69..aa4920d6 100644 --- a/backend/src/plugins/Utility/refreshMembers.ts +++ b/backend/src/plugins/Utility/refreshMembers.ts @@ -1,4 +1,4 @@ -import { Guild } from "eris"; +import { Guild } from "discord.js"; import { HOURS, noop } from "../../utils"; const MEMBER_REFRESH_FREQUENCY = 1 * HOURS; // How often to do a full member refresh when using commands that need it @@ -10,7 +10,7 @@ export async function refreshMembersIfNeeded(guild: Guild) { return lastRefresh.promise; } - const loadPromise = guild.fetchAllMembers().then(noop); + const loadPromise = guild.members.fetch().then(noop); memberRefreshLog.set(guild.id, { time: Date.now(), promise: loadPromise, diff --git a/backend/src/plugins/Utility/search.ts b/backend/src/plugins/Utility/search.ts index 751d42ad..4717dd31 100644 --- a/backend/src/plugins/Utility/search.ts +++ b/backend/src/plugins/Utility/search.ts @@ -1,20 +1,30 @@ -import { Constants, Member, Message, User } from "eris"; -import moment from "moment-timezone"; +import { + GuildMember, + Message, + MessageActionRow, + MessageButton, + MessageComponentInteraction, + Permissions, + Snowflake, + TextChannel, + User, +} from "discord.js"; import escapeStringRegexp from "escape-string-regexp"; -import { isFullMessage, MINUTES, multiSorter, noop, sorter, trimLines } from "../../utils"; -import { getBaseUrl, sendErrorMessage } from "../../pluginUtils"; import { GuildPluginData } from "knub"; import { ArgsFromSignatureOrArray } from "knub/dist/commands/commandUtils"; -import { searchCmdSignature } from "./commands/SearchCmd"; -import { banSearchSignature } from "./commands/BanSearchCmd"; -import { UtilityPluginType } from "./types"; -import { refreshMembersIfNeeded } from "./refreshMembers"; -import { getUserInfoEmbed } from "./functions/getUserInfoEmbed"; +import moment from "moment-timezone"; +import { getBaseUrl, sendErrorMessage } from "../../pluginUtils"; import { allowTimeout, RegExpRunner } from "../../RegExpRunner"; -import { inputPatternToRegExp, InvalidRegexError } from "../../validatorUtils"; +import { MINUTES, multiSorter, sorter, trimLines } from "../../utils"; import { asyncFilter } from "../../utils/async"; -import Timeout = NodeJS.Timeout; import { hasDiscordPermissions } from "../../utils/hasDiscordPermissions"; +import { inputPatternToRegExp, InvalidRegexError } from "../../validatorUtils"; +import { banSearchSignature } from "./commands/BanSearchCmd"; +import { searchCmdSignature } from "./commands/SearchCmd"; +import { getUserInfoEmbed } from "./functions/getUserInfoEmbed"; +import { refreshMembersIfNeeded } from "./refreshMembers"; +import { UtilityPluginType } from "./types"; +import Timeout = NodeJS.Timeout; const SEARCH_RESULTS_PER_PAGE = 15; const SEARCH_ID_RESULTS_PER_PAGE = 50; @@ -75,9 +85,8 @@ export async function displaySearch( let originalSearchMsg: Message; let searching = false; let currentPage = args.page || 1; - let hasReactions = false; - let clearReactionsFn: () => void; - let clearReactionsTimeout: Timeout; + let stopCollectionFn: () => void; + let stopCollectionTimeout: Timeout; const perPage = args.ids ? SEARCH_ID_RESULTS_PER_PAGE : SEARCH_RESULTS_PER_PAGE; @@ -91,7 +100,7 @@ export async function displaySearch( if (originalSearchMsg) { searchMsgPromise = originalSearchMsg.edit("Searching..."); } else { - searchMsgPromise = msg.channel.createMessage("Searching..."); + searchMsgPromise = msg.channel.send("Searching..."); searchMsgPromise.then(m => (originalSearchMsg = m)); } @@ -107,12 +116,12 @@ export async function displaySearch( } } catch (e) { if (e instanceof SearchError) { - sendErrorMessage(pluginData, msg.channel, e.message); + sendErrorMessage(pluginData, msg.channel as TextChannel, e.message); return; } if (e instanceof InvalidRegexError) { - sendErrorMessage(pluginData, msg.channel, e.message); + sendErrorMessage(pluginData, msg.channel as TextChannel, e.message); return; } @@ -120,7 +129,7 @@ export async function displaySearch( } if (searchResult.totalResults === 0) { - sendErrorMessage(pluginData, msg.channel, "No results found"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "No results found"); return; } @@ -150,50 +159,73 @@ export async function displaySearch( const embed = await getUserInfoEmbed(pluginData, searchResult.results[0].id, false); if (embed) { searchMsg.edit("Only one result:"); - msg.channel.createMessage({ embed }); + msg.channel.send({ embeds: [embed] }); return; } } - searchMsg.edit(result); + currentPage = searchResult.page; // Set up pagination reactions if needed. The reactions are cleared after a timeout. if (searchResult.totalResults > perPage) { - if (!hasReactions) { - hasReactions = true; - searchMsg.addReaction("⬅"); - searchMsg.addReaction("➡"); - searchMsg.addReaction("🔄"); + const idMod = `${searchMsg.id}:${moment.utc().valueOf()}`; + const buttons: MessageButton[] = []; - const listenerFn = pluginData.events.on("messageReactionAdd", ({ args: { message: rMsg, emoji, member } }) => { - if (rMsg.id !== searchMsg.id) return; - if (member.id !== msg.author.id) return; - if (!["⬅", "➡", "🔄"].includes(emoji.name)) return; + buttons.push( + new MessageButton() + .setStyle("SECONDARY") + .setEmoji("⬅") + .setCustomId(`previousButton:${idMod}`) + .setDisabled(currentPage === 1), + new MessageButton() + .setStyle("SECONDARY") + .setEmoji("➡") + .setCustomId(`nextButton:${idMod}`) + .setDisabled(currentPage === searchResult.lastPage), + new MessageButton() + .setStyle("SECONDARY") + .setEmoji("🔄") + .setCustomId(`reloadButton:${idMod}`), + ); - if (emoji.name === "⬅" && currentPage > 1) { - loadSearchPage(currentPage - 1); - } else if (emoji.name === "➡" && currentPage < searchResult.lastPage) { - loadSearchPage(currentPage + 1); - } else if (emoji.name === "🔄") { - loadSearchPage(currentPage); + const row = new MessageActionRow().addComponents(buttons); + await searchMsg.edit({ content: result, components: [row] }); + + const collector = searchMsg.createMessageComponentCollector({ time: 2 * MINUTES }); + + collector.on("collect", async (interaction: MessageComponentInteraction) => { + if (msg.author.id !== interaction.user.id) { + interaction.reply({ content: `You are not permitted to use these buttons.`, ephemeral: true }); + } else { + if (interaction.customId === `previousButton:${idMod}` && currentPage > 1) { + collector.stop(); + await interaction.deferUpdate(); + await loadSearchPage(currentPage - 1); + } else if (interaction.customId === `nextButton:${idMod}` && currentPage < searchResult.lastPage) { + collector.stop(); + await interaction.deferUpdate(); + await loadSearchPage(currentPage + 1); + } else if (interaction.customId === `reloadButton:${idMod}`) { + collector.stop(); + await interaction.deferUpdate(); + await loadSearchPage(currentPage); + } else { + await interaction.deferUpdate(); } + } + }); - if (isFullMessage(rMsg)) { - rMsg.removeReaction(emoji.name, member.id); - } - }); + stopCollectionFn = async () => { + collector.stop(); + await searchMsg.edit({ content: searchMsg.content, components: [] }); + }; - clearReactionsFn = async () => { - searchMsg.removeReactions().catch(noop); - pluginData.events.off("messageReactionAdd", listenerFn); - }; - } - - clearTimeout(clearReactionsTimeout); - clearReactionsTimeout = setTimeout(clearReactionsFn, 5 * MINUTES); + clearTimeout(stopCollectionTimeout); + stopCollectionTimeout = setTimeout(stopCollectionFn, 2 * MINUTES); + } else { + searchMsg.edit(result); } - currentPage = searchResult.page; searching = false; }; @@ -230,12 +262,12 @@ export async function archiveSearch( } } catch (e) { if (e instanceof SearchError) { - sendErrorMessage(pluginData, msg.channel, e.message); + sendErrorMessage(pluginData, msg.channel as TextChannel, e.message); return; } if (e instanceof InvalidRegexError) { - sendErrorMessage(pluginData, msg.channel, e.message); + sendErrorMessage(pluginData, msg.channel as TextChannel, e.message); return; } @@ -243,7 +275,7 @@ export async function archiveSearch( } if (results.totalResults === 0) { - sendErrorMessage(pluginData, msg.channel, "No results found"); + sendErrorMessage(pluginData, msg.channel as TextChannel, "No results found"); return; } @@ -261,7 +293,7 @@ export async function archiveSearch( const baseUrl = getBaseUrl(pluginData); const url = await pluginData.state.archives.getUrl(baseUrl, archiveId); - await msg.channel.createMessage(`Exported search results: ${url}`); + await msg.channel.send(`Exported search results: ${url}`); } async function performMemberSearch( @@ -269,16 +301,16 @@ async function performMemberSearch( args: MemberSearchParams, page = 1, perPage = SEARCH_RESULTS_PER_PAGE, -): Promise<{ results: Member[]; totalResults: number; page: number; lastPage: number; from: number; to: number }> { +): Promise<{ results: GuildMember[]; totalResults: number; page: number; lastPage: number; from: number; to: number }> { await refreshMembersIfNeeded(pluginData.guild); - let matchingMembers = Array.from(pluginData.guild.members.values()); + let matchingMembers = Array.from(pluginData.guild.members.cache.values()); if (args.role) { const roleIds = args.role.split(","); matchingMembers = matchingMembers.filter(member => { for (const role of roleIds) { - if (!member.roles.includes(role)) return false; + if (!member.roles.cache.has(role as Snowflake)) return false; } return true; @@ -286,11 +318,11 @@ async function performMemberSearch( } if (args.voice) { - matchingMembers = matchingMembers.filter(m => m.voiceState.channelID != null); + matchingMembers = matchingMembers.filter(m => m.voice.channelId); } if (args.bot) { - matchingMembers = matchingMembers.filter(m => m.bot); + matchingMembers = matchingMembers.filter(m => m.user.bot); } if (args.query) { @@ -307,6 +339,7 @@ async function performMemberSearch( const execRegExp = getOptimizedRegExpRunner(pluginData, isSafeRegex); + /* FIXME if we ever get the intent for this again if (args["status-search"]) { matchingMembers = await asyncFilter(matchingMembers, async member => { if (member.game) { @@ -345,17 +378,18 @@ async function performMemberSearch( return false; }); } else { - matchingMembers = await asyncFilter(matchingMembers, async member => { - if (member.nick && (await execRegExp(queryRegex, member.nick).catch(allowTimeout))) { - return true; - } + */ + matchingMembers = await asyncFilter(matchingMembers, async member => { + if (member.nickname && (await execRegExp(queryRegex, member.nickname).catch(allowTimeout))) { + return true; + } - const fullUsername = `${member.user.username}#${member.user.discriminator}`; - if (await execRegExp(queryRegex, fullUsername).catch(allowTimeout)) return true; + const fullUsername = member.user.tag; + if (await execRegExp(queryRegex, fullUsername).catch(allowTimeout)) return true; - return false; - }); - } + return false; + }); + // } FIXME in conjunction with above comment } const [, sortDir, sortBy] = (args.sort && args.sort.match(/^(-?)(.*)$/)) ?? [null, "ASC", "name"]; @@ -366,7 +400,7 @@ async function performMemberSearch( } else { matchingMembers.sort( multiSorter([ - [m => m.username.toLowerCase(), realSortDir], + [m => m.user.username.toLowerCase(), realSortDir], [m => m.discriminator, realSortDir], ]), ); @@ -396,12 +430,12 @@ async function performBanSearch( page = 1, perPage = SEARCH_RESULTS_PER_PAGE, ): Promise<{ results: User[]; totalResults: number; page: number; lastPage: number; from: number; to: number }> { - const member = pluginData.guild.members.get(pluginData.client.user.id); - if (member && !hasDiscordPermissions(member.permissions, Constants.Permissions.banMembers)) { + const member = pluginData.guild.members.cache.get(pluginData.client.user!.id); + if (member && !hasDiscordPermissions(member.permissions, Permissions.FLAGS.BAN_MEMBERS)) { throw new SearchError(`Unable to search bans: missing "Ban Members" permission`); } - let matchingBans = (await pluginData.guild.getBans()).map(x => x.user); + let matchingBans = (await pluginData.guild.bans.fetch({ cache: false })).map(x => x.user); if (args.query) { let isSafeRegex = true; @@ -417,7 +451,7 @@ async function performBanSearch( const execRegExp = getOptimizedRegExpRunner(pluginData, isSafeRegex); matchingBans = await asyncFilter(matchingBans, async user => { - const fullUsername = `${user.username}#${user.discriminator}`; + const fullUsername = user.tag; if (await execRegExp(queryRegex, fullUsername).catch(allowTimeout)) return true; return false; }); @@ -455,22 +489,22 @@ async function performBanSearch( }; } -function formatSearchResultList(members: Array): string { +function formatSearchResultList(members: Array): string { const longestId = members.reduce((longest, member) => Math.max(longest, member.id.length), 0); const lines = members.map(member => { const paddedId = member.id.padEnd(longestId, " "); let line; - if (member instanceof Member) { - line = `${paddedId} ${member.user.username}#${member.user.discriminator}`; - if (member.nick) line += ` (${member.nick})`; + if (member instanceof GuildMember) { + line = `${paddedId} ${member.user.tag}`; + if (member.nickname) line += ` (${member.nickname})`; } else { - line = `${paddedId} ${member.username}#${member.discriminator}`; + line = `${paddedId} ${member.tag}`; } return line; }); return lines.join("\n"); } -function formatSearchResultIdList(members: Array): string { +function formatSearchResultIdList(members: Array): string { return members.map(m => m.id).join(" "); } diff --git a/backend/src/plugins/Utility/types.ts b/backend/src/plugins/Utility/types.ts index bea8f6c3..15f6e925 100644 --- a/backend/src/plugins/Utility/types.ts +++ b/backend/src/plugins/Utility/types.ts @@ -1,9 +1,9 @@ import * as t from "io-ts"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub"; -import { GuildLogs } from "../../data/GuildLogs"; -import { GuildCases } from "../../data/GuildCases"; -import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildArchives } from "../../data/GuildArchives"; +import { GuildCases } from "../../data/GuildCases"; +import { GuildLogs } from "../../data/GuildLogs"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { Supporters } from "../../data/Supporters"; import { RegExpRunner } from "../../RegExpRunner"; @@ -34,6 +34,7 @@ export const ConfigSchema = t.type({ jumbo_size: t.Integer, can_avatar: t.boolean, info_on_single_result: t.boolean, + autojoin_threads: t.boolean, }); export type TConfigSchema = t.TypeOf; diff --git a/backend/src/plugins/WelcomeMessage/WelcomeMessagePlugin.ts b/backend/src/plugins/WelcomeMessage/WelcomeMessagePlugin.ts index a5899a03..c311c794 100644 --- a/backend/src/plugins/WelcomeMessage/WelcomeMessagePlugin.ts +++ b/backend/src/plugins/WelcomeMessage/WelcomeMessagePlugin.ts @@ -1,8 +1,8 @@ -import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { PluginOptions } from "knub"; -import { ConfigSchema, WelcomeMessagePluginType } from "./types"; import { GuildLogs } from "../../data/GuildLogs"; +import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { SendWelcomeMessageEvt } from "./events/SendWelcomeMessageEvt"; +import { ConfigSchema, WelcomeMessagePluginType } from "./types"; const defaultOptions: PluginOptions = { config: { diff --git a/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts b/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts index 9ae1e85f..852c3650 100644 --- a/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts +++ b/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts @@ -1,9 +1,14 @@ -import { welcomeMessageEvt } from "../types"; +import { Snowflake, TextChannel } from "discord.js"; +import { + channelToConfigAccessibleChannel, + memberToConfigAccessibleMember, + userToConfigAccessibleUser, +} from "../../../utils/configAccessibleObjects"; +import { LogType } from "../../../data/LogType"; import { renderTemplate, TemplateParseError } from "../../../templateFormatter"; import { createChunkedMessage, stripObjectToScalars } from "../../../utils"; -import { LogType } from "../../../data/LogType"; -import { TextChannel } from "eris"; import { sendDM } from "../../../utils/sendDM"; +import { welcomeMessageEvt } from "../types"; export const SendWelcomeMessageEvt = welcomeMessageEvt({ event: "guildMemberAdd", @@ -49,13 +54,13 @@ export const SendWelcomeMessageEvt = welcomeMessageEvt({ } catch { pluginData.state.logs.log(LogType.DM_FAILED, { source: "welcome message", - user: stripObjectToScalars(member.user), + user: userToConfigAccessibleUser(member.user), }); } } if (config.send_to_channel) { - const channel = meta.args.guild.channels.get(config.send_to_channel); + const channel = meta.args.member.guild.channels.cache.get(config.send_to_channel as Snowflake); if (!channel || !(channel instanceof TextChannel)) return; try { @@ -63,8 +68,8 @@ export const SendWelcomeMessageEvt = welcomeMessageEvt({ } catch { pluginData.state.logs.log(LogType.BOT_ALERT, { body: `Failed send a welcome message for {userMention(member)} to {channelMention(channel)}`, - member: stripObjectToScalars(member), - channel: stripObjectToScalars(channel), + member: memberToConfigAccessibleMember(member), + channel: channelToConfigAccessibleChannel(channel), }); } } diff --git a/backend/src/plugins/WelcomeMessage/types.ts b/backend/src/plugins/WelcomeMessage/types.ts index 7f99b402..c4a995da 100644 --- a/backend/src/plugins/WelcomeMessage/types.ts +++ b/backend/src/plugins/WelcomeMessage/types.ts @@ -1,7 +1,7 @@ import * as t from "io-ts"; import { BasePluginType, typedGuildEventListener } from "knub"; -import { tNullable } from "../../utils"; import { GuildLogs } from "../../data/GuildLogs"; +import { tNullable } from "../../utils"; export const ConfigSchema = t.type({ send_dm: t.boolean, diff --git a/backend/src/plugins/ZeppelinPluginBlueprint.ts b/backend/src/plugins/ZeppelinPluginBlueprint.ts index f55bd0c6..7a1dd44a 100644 --- a/backend/src/plugins/ZeppelinPluginBlueprint.ts +++ b/backend/src/plugins/ZeppelinPluginBlueprint.ts @@ -1,17 +1,17 @@ +import * as t from "io-ts"; import { BasePluginType, - typedGlobalPlugin, GlobalPluginBlueprint, GlobalPluginData, - typedGuildPlugin, GuildPluginBlueprint, GuildPluginData, + typedGlobalPlugin, + typedGuildPlugin, } from "knub"; -import * as t from "io-ts"; +import { PluginOptions } from "knub/dist/config/configTypes"; +import { Awaitable } from "knub/dist/utils"; import { getPluginConfigPreprocessor } from "../pluginUtils"; import { TMarkdown } from "../types"; -import { Awaitable } from "knub/dist/utils"; -import { PluginOptions } from "knub/dist/config/configTypes"; /** * GUILD PLUGINS diff --git a/backend/src/plugins/availablePlugins.ts b/backend/src/plugins/availablePlugins.ts index e1e4e936..34535b7c 100644 --- a/backend/src/plugins/availablePlugins.ts +++ b/backend/src/plugins/availablePlugins.ts @@ -1,38 +1,39 @@ -import { UtilityPlugin } from "./Utility/UtilityPlugin"; -import { LocateUserPlugin } from "./LocateUser/LocateUserPlugin"; -import { ZeppelinGlobalPluginBlueprint, ZeppelinGuildPluginBlueprint } from "./ZeppelinPluginBlueprint"; -import { PersistPlugin } from "./Persist/PersistPlugin"; -import { NameHistoryPlugin } from "./NameHistory/NameHistoryPlugin"; -import { MessageSaverPlugin } from "./MessageSaver/MessageSaverPlugin"; -import { AutoReactionsPlugin } from "./AutoReactions/AutoReactionsPlugin"; -import { RemindersPlugin } from "./Reminders/RemindersPlugin"; -import { UsernameSaverPlugin } from "./UsernameSaver/UsernameSaverPlugin"; -import { WelcomeMessagePlugin } from "./WelcomeMessage/WelcomeMessagePlugin"; -import { PingableRolesPlugin } from "./PingableRoles/PingableRolesPlugin"; -import { GuildConfigReloaderPlugin } from "./GuildConfigReloader/GuildConfigReloaderPlugin"; -import { CasesPlugin } from "./Cases/CasesPlugin"; -import { MutesPlugin } from "./Mutes/MutesPlugin"; -import { TagsPlugin } from "./Tags/TagsPlugin"; -import { ModActionsPlugin } from "./ModActions/ModActionsPlugin"; -import { PostPlugin } from "./Post/PostPlugin"; import { AutoDeletePlugin } from "./AutoDelete/AutoDeletePlugin"; -import { GuildInfoSaverPlugin } from "./GuildInfoSaver/GuildInfoSaverPlugin"; -import { CensorPlugin } from "./Censor/CensorPlugin"; -import { RolesPlugin } from "./Roles/RolesPlugin"; -import { SlowmodePlugin } from "./Slowmode/SlowmodePlugin"; -import { StarboardPlugin } from "./Starboard/StarboardPlugin"; -import { ChannelArchiverPlugin } from "./ChannelArchiver/ChannelArchiverPlugin"; -import { LogsPlugin } from "./Logs/LogsPlugin"; -import { SelfGrantableRolesPlugin } from "./SelfGrantableRoles/SelfGrantableRolesPlugin"; -import { SpamPlugin } from "./Spam/SpamPlugin"; -import { ReactionRolesPlugin } from "./ReactionRoles/ReactionRolesPlugin"; import { AutomodPlugin } from "./Automod/AutomodPlugin"; -import { CompanionChannelsPlugin } from "./CompanionChannels/CompanionChannelsPlugin"; -import { CustomEventsPlugin } from "./CustomEvents/CustomEventsPlugin"; +import { AutoReactionsPlugin } from "./AutoReactions/AutoReactionsPlugin"; import { BotControlPlugin } from "./BotControl/BotControlPlugin"; -import { GuildAccessMonitorPlugin } from "./GuildAccessMonitor/GuildAccessMonitorPlugin"; -import { TimeAndDatePlugin } from "./TimeAndDate/TimeAndDatePlugin"; +import { CasesPlugin } from "./Cases/CasesPlugin"; +import { CensorPlugin } from "./Censor/CensorPlugin"; +import { ChannelArchiverPlugin } from "./ChannelArchiver/ChannelArchiverPlugin"; +import { CompanionChannelsPlugin } from "./CompanionChannels/CompanionChannelsPlugin"; +import { ContextMenuPlugin } from "./ContextMenus/ContextMenuPlugin"; import { CountersPlugin } from "./Counters/CountersPlugin"; +import { CustomEventsPlugin } from "./CustomEvents/CustomEventsPlugin"; +import { GuildAccessMonitorPlugin } from "./GuildAccessMonitor/GuildAccessMonitorPlugin"; +import { GuildConfigReloaderPlugin } from "./GuildConfigReloader/GuildConfigReloaderPlugin"; +import { GuildInfoSaverPlugin } from "./GuildInfoSaver/GuildInfoSaverPlugin"; +import { LocateUserPlugin } from "./LocateUser/LocateUserPlugin"; +import { LogsPlugin } from "./Logs/LogsPlugin"; +import { MessageSaverPlugin } from "./MessageSaver/MessageSaverPlugin"; +import { ModActionsPlugin } from "./ModActions/ModActionsPlugin"; +import { MutesPlugin } from "./Mutes/MutesPlugin"; +import { NameHistoryPlugin } from "./NameHistory/NameHistoryPlugin"; +import { PersistPlugin } from "./Persist/PersistPlugin"; +import { PingableRolesPlugin } from "./PingableRoles/PingableRolesPlugin"; +import { PostPlugin } from "./Post/PostPlugin"; +import { ReactionRolesPlugin } from "./ReactionRoles/ReactionRolesPlugin"; +import { RemindersPlugin } from "./Reminders/RemindersPlugin"; +import { RolesPlugin } from "./Roles/RolesPlugin"; +import { SelfGrantableRolesPlugin } from "./SelfGrantableRoles/SelfGrantableRolesPlugin"; +import { SlowmodePlugin } from "./Slowmode/SlowmodePlugin"; +import { SpamPlugin } from "./Spam/SpamPlugin"; +import { StarboardPlugin } from "./Starboard/StarboardPlugin"; +import { TagsPlugin } from "./Tags/TagsPlugin"; +import { TimeAndDatePlugin } from "./TimeAndDate/TimeAndDatePlugin"; +import { UsernameSaverPlugin } from "./UsernameSaver/UsernameSaverPlugin"; +import { UtilityPlugin } from "./Utility/UtilityPlugin"; +import { WelcomeMessagePlugin } from "./WelcomeMessage/WelcomeMessagePlugin"; +import { ZeppelinGlobalPluginBlueprint, ZeppelinGuildPluginBlueprint } from "./ZeppelinPluginBlueprint"; // prettier-ignore export const guildPlugins: Array> = [ @@ -67,6 +68,7 @@ export const guildPlugins: Array> = [ CustomEventsPlugin, TimeAndDatePlugin, CountersPlugin, + ContextMenuPlugin, ]; // prettier-ignore diff --git a/backend/src/templateFormatter.test.ts b/backend/src/templateFormatter.test.ts index 93a804b9..0d692e8b 100644 --- a/backend/src/templateFormatter.test.ts +++ b/backend/src/templateFormatter.test.ts @@ -1,5 +1,5 @@ -import { parseTemplate, renderParsedTemplate, renderTemplate } from "./templateFormatter"; import test from "ava"; +import { parseTemplate, renderParsedTemplate, renderTemplate } from "./templateFormatter"; test("Parses plain string templates correctly", t => { const result = parseTemplate("foo bar baz"); diff --git a/backend/src/templateFormatter.ts b/backend/src/templateFormatter.ts index 865da3d2..145d7f1d 100644 --- a/backend/src/templateFormatter.ts +++ b/backend/src/templateFormatter.ts @@ -1,5 +1,5 @@ -import { get, has } from "./utils"; import seedrandom from "seedrandom"; +import { get, has } from "./utils"; const TEMPLATE_CACHE_SIZE = 200; const templateCache: Map = new Map(); diff --git a/backend/src/types.ts b/backend/src/types.ts index d9fc3803..9f9c1a53 100644 --- a/backend/src/types.ts +++ b/backend/src/types.ts @@ -1,6 +1,5 @@ -import { BaseConfig, Knub } from "knub"; import * as t from "io-ts"; -import { Message } from "eris"; +import { BaseConfig, Knub } from "knub"; export interface ZeppelinGuildConfig extends BaseConfig { success_emoji?: string; @@ -64,3 +63,40 @@ export interface CommandInfo { [key: string]: TMarkdown; }; } + +export enum ChannelTypeStrings { + TEXT = "GUILD_TEXT", + DM = "DM", + VOICE = "GUILD_VOICE", + GROUP = "GROUP_DM", + CATEGORY = "GUILD_CATEGORY", + NEWS = "GUILD_NEWS", + STORE = "GUILD_STORE", + NEWS_THREAD = "GUILD_NEWS_THREAD", + PUBLIC_THREAD = "GUILD_PUBLIC_THREAD", + PRIVATE_THREAD = "GUILD_PRIVATE_THREAD", + STAGE = "GUILD_STAGE_VOICE", + UNKNOWN = "UNKNOWN", +} + +export enum MessageTypeStrings { + "DEFAULT", + "RECIPIENT_ADD", + "RECIPIENT_REMOVE", + "CALL", + "CHANNEL_NAME_CHANGE", + "CHANNEL_ICON_CHANGE", + "PINS_ADD", + "GUILD_MEMBER_JOIN", + "USER_PREMIUM_GUILD_SUBSCRIPTION", + "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1", + "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2", + "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3", + "CHANNEL_FOLLOW_ADD", + "GUILD_DISCOVERY_DISQUALIFIED", + "GUILD_DISCOVERY_REQUALIFIED", + "GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING", + "GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING", + "REPLY", + "APPLICATION_COMMAND", +} diff --git a/backend/src/utils.test.ts b/backend/src/utils.test.ts index 9d72ce0f..abd89774 100644 --- a/backend/src/utils.test.ts +++ b/backend/src/utils.test.ts @@ -1,8 +1,7 @@ +import test from "ava"; import * as ioTs from "io-ts"; import { convertDelayStringToMS, convertMSToDelayString, getUrlsInString, tAllowedMentions } from "./utils"; - -import test from "ava"; -import { AllowedMentions as ErisAllowedMentions } from "eris"; +import { ErisAllowedMentionFormat } from "./utils/erisAllowedMentionsToDjsMentionOptions"; type AssertEquals = TActual extends TExpected ? true : false; @@ -52,6 +51,6 @@ test("delay strings: reverse conversion (conservative)", t => { test("tAllowedMentions matches Eris's AllowedMentions", t => { type TAllowedMentions = ioTs.TypeOf; - const typeTest: AssertEquals = true; + const typeTest: AssertEquals = true; t.pass(); }); diff --git a/backend/src/utils.ts b/backend/src/utils.ts index 74e5c4a2..6f0edca8 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -1,43 +1,47 @@ import { - AllowedMentions, - Attachment, Client, Constants, - Embed, - EmbedOptions, Emoji, Guild, - GuildAuditLog, - GuildAuditLogEntry, + GuildAuditLogs, + GuildAuditLogsEntry, GuildChannel, + GuildMember, Invite, - InvitePartialChannel, - Member, + LimitedCollection, Message, - MessageContent, - PossiblyUncachedMessage, - TextableChannel, + MessageAttachment, + MessageEmbed, + MessageEmbedOptions, + MessageMentionOptions, + MessageOptions, + PartialChannelData, + PartialMessage, + Snowflake, + Sticker, TextChannel, + ThreadChannel, User, -} from "eris"; -import { URL } from "url"; -import tlds from "tlds"; + Util, +} from "discord.js"; import emojiRegex from "emoji-regex"; -import * as t from "io-ts"; - +import { either } from "fp-ts/lib/Either"; +import { unsafeCoerce } from "fp-ts/lib/function"; import fs from "fs"; import https from "https"; -import tmp from "tmp"; -import { helpers } from "knub"; -import { SavedMessage } from "./data/entities/SavedMessage"; -import { decodeAndValidateStrict, StrictValidationError } from "./validatorUtils"; -import { either } from "fp-ts/lib/Either"; +import humanizeDuration from "humanize-duration"; +import * as t from "io-ts"; +import { isEqual } from "lodash"; import moment from "moment-timezone"; +import tlds from "tlds"; +import tmp from "tmp"; +import { URL } from "url"; +import { SavedMessage } from "./data/entities/SavedMessage"; import { SimpleCache } from "./SimpleCache"; -import { logger } from "./logger"; -import { unsafeCoerce } from "fp-ts/lib/function"; +import { ChannelTypeStrings } from "./types"; import { sendDM } from "./utils/sendDM"; -import { LogType } from "./data/LogType"; +import { waitForButtonConfirm } from "./utils/waitForInteraction"; +import { decodeAndValidateStrict, StrictValidationError } from "./validatorUtils"; const fsp = fs.promises; @@ -73,13 +77,13 @@ export function isValidSnowflake(str: string) { } export const DISCORD_HTTP_ERROR_NAME = "DiscordHTTPError"; -export const DISCORD_REST_ERROR_NAME = "DiscordRESTError"; +export const DISCORD_REST_ERROR_NAME = "DiscordAPIError"; export function isDiscordHTTPError(err: Error | string) { return typeof err === "object" && err.constructor?.name === DISCORD_HTTP_ERROR_NAME; } -export function isDiscordRESTError(err: Error | string) { +export function isDiscordAPIError(err: Error | string) { return typeof err === "object" && err.constructor?.name === DISCORD_REST_ERROR_NAME; } @@ -168,6 +172,52 @@ function tDeepPartialProp(prop: any) { } } +export function getScalarDifference( + base: T, + object: T, + ignoreKeys: string[] = [], +): Map { + base = stripObjectToScalars(base) as T; + object = stripObjectToScalars(object) as T; + const diff = new Map(); + + for (const [key, value] of Object.entries(object)) { + if (!isEqual(value, base[key]) && !ignoreKeys.includes(key)) { + diff.set(key, { was: base[key], is: value }); + } + } + + return diff; +} + +// This is a stupid, messy solution that is not extendable at all. +// If anyone plans on adding anything to this, they should rewrite this first. +// I just want to get this done and this works for now :) +export function prettyDifference(diff: Map): Map { + const toReturn = new Map(); + + for (let [key, difference] of diff) { + if (key === "rateLimitPerUser") { + difference.is = humanizeDuration(difference.is * 1000); + difference.was = humanizeDuration(difference.was * 1000); + key = "slowmode"; + } + + toReturn.set(key, { was: difference.was, is: difference.is }); + } + + return toReturn; +} + +export function differenceToString(diff: Map): string { + let toReturn = ""; + diff = prettyDifference(diff); + for (const [key, difference] of diff) { + toReturn += `**${key[0].toUpperCase() + key.slice(1)}**: \`${difference.was}\` ➜ \`${difference.is}\`\n`; + } + return toReturn; +} + // https://stackoverflow.com/a/49262929/316944 export type Not = T & Exclude; @@ -199,9 +249,9 @@ export function nonNullish(v: V): v is NonNullable { } export type InviteOpts = "withMetadata" | "withCount" | "withoutCount"; -export type GuildInvite = Invite & { guild: Guild }; -export type GroupDMInvite = Invite & { - channel: InvitePartialChannel; +export type GuildInvite = Invite & { guild: Guild }; +export type GroupDMInvite = Invite & { + channel: PartialChannelData; type: typeof Constants.ChannelTypes.GROUP_DM; }; @@ -269,9 +319,15 @@ export const tEmbed = t.type({ ), }); -export type EmbedWith = EmbedOptions & Pick, T>; +export type EmbedWith = MessageEmbedOptions & + Pick, T>; -export type StrictMessageContent = { content?: string; tts?: boolean; disableEveryone?: boolean; embed?: EmbedOptions }; +export type StrictMessageContent = { + content?: string; + tts?: boolean; + disableEveryone?: boolean; + embed?: MessageEmbedOptions; +}; export const tStrictMessageContent = t.type({ content: tNullable(t.string), @@ -458,16 +514,16 @@ export async function findRelevantAuditLogEntry( userId: string, attempts: number = 3, attemptDelay: number = 3000, -): Promise { +): Promise { if (auditLogNextAttemptAfterFail.has(guild.id) && auditLogNextAttemptAfterFail.get(guild.id)! > Date.now()) { return null; } - let auditLogs: GuildAuditLog | null = null; + let auditLogs: GuildAuditLogs | null = null; try { - auditLogs = await guild.getAuditLogs(5, undefined, actionType); + auditLogs = await guild.fetchAuditLogs({ limit: 5, type: actionType }); } catch (e) { - if (isDiscordRESTError(e) && e.code === 50013) { + if (isDiscordAPIError(e) && e.code === 50013) { // If we don't have permission to read audit log, set audit log requests on cooldown auditLogNextAttemptAfterFail.set(guild.id, Date.now() + AUDIT_LOG_FAIL_COOLDOWN); } else if (isDiscordHTTPError(e) && e.code === 500) { @@ -479,7 +535,7 @@ export async function findRelevantAuditLogEntry( } } - const entries = auditLogs ? auditLogs.entries : []; + const entries = auditLogs ? [...auditLogs.entries.values()] : []; entries.sort((a, b) => { if (a.createdAt > b.createdAt) return -1; @@ -490,7 +546,7 @@ export async function findRelevantAuditLogEntry( const cutoffTS = Date.now() - 1000 * 60 * 2; const relevantEntry = entries.find(entry => { - return entry.targetID === userId && entry.createdAt >= cutoffTS; + return (entry.target as { id }).id === userId && entry.createdTimestamp >= cutoffTS; }); if (relevantEntry) { @@ -730,21 +786,6 @@ export function deactivateMentions(content: string): string { return content.replace(/@/g, "@\u200b"); } -/** - * Disable inline code in the given string by replacing backticks/grave accents with acute accents - * FIXME: Find a better way that keeps the grave accents? Can't use the code block approach here since it's just 1 character. - */ -export function disableInlineCode(content: string): string { - return content.replace(/`/g, "\u00b4"); -} - -/** - * Disable code blocks in the given string by adding invisible unicode characters between backticks - */ -export function disableCodeBlocks(content: string): string { - return content.replace(/`/g, "`\u200b"); -} - export function useMediaUrls(content: string): string { return content.replace(/cdn\.discord(app)?\.com/g, "media.discordapp.net"); } @@ -830,13 +871,13 @@ export function chunkMessageLines(str: string, maxChunkLength = 1990): string[] } export async function createChunkedMessage( - channel: TextableChannel, + channel: TextChannel | User, messageText: string, - allowedMentions?: AllowedMentions, + allowedMentions?: MessageMentionOptions, ) { const chunks = chunkMessageLines(messageText); for (const chunk of chunks) { - await channel.createMessage({ content: chunk, allowedMentions }); + await channel.send({ content: chunk, allowedMentions }); } } @@ -964,7 +1005,7 @@ export type CustomEmoji = { id: string; } & Emoji; -export type UserNotificationMethod = { type: "dm" } | { type: "channel"; channel: TextChannel }; +export type UserNotificationMethod = { type: "dm" } | { type: "channel"; channel: TextChannel | ThreadChannel }; export const disableUserNotificationStrings = ["no", "none", "off"]; @@ -1011,7 +1052,7 @@ export async function notifyUser( } } else if (method.type === "channel") { try { - await method.channel.createMessage({ + await method.channel.send({ content: `<@!${user.id}> ${body}`, allowedMentions: { users: [user.id] }, }); @@ -1044,6 +1085,7 @@ export class UnknownUser { public id: string; public username = "Unknown"; public discriminator = "0000"; + public tag = "Unknown#0000"; constructor(props = {}) { for (const key in props) { @@ -1112,7 +1154,7 @@ export function resolveUserId(bot: Client, value: string) { // A non-mention, full username? const usernameMatch = value.match(/^@?([^#]+)#(\d{4})$/); if (usernameMatch) { - const user = bot.users.find(u => u.username === usernameMatch[1] && u.discriminator === usernameMatch[2]); + const user = bot.users.cache.find(u => u.username === usernameMatch[1] && u.discriminator === usernameMatch[2]); if (user) return user.id; } @@ -1130,7 +1172,7 @@ export function resolveUserId(bot: Client, value: string) { */ export function getUser(client: Client, userResolvable: string): User | UnknownUser { const id = resolveUserId(client, userResolvable); - return id ? client.users.get(id) || new UnknownUser({ id }) : new UnknownUser(); + return id ? client.users.resolve(id as Snowflake) || new UnknownUser({ id }) : new UnknownUser(); } /** @@ -1150,8 +1192,8 @@ export async function resolveUser(bot, value) { } // If we have the user cached, return that directly - if (bot.users.has(userId)) { - return bot.users.get(userId); + if (bot.users.cache.has(userId)) { + return bot.users.fetch(userId); } // We don't want to spam the API by trying to fetch unknown users again and again, @@ -1160,9 +1202,8 @@ export async function resolveUser(bot, value) { return new UnknownUser({ id: userId }); } - const freshUser = await bot.getRESTUser(userId).catch(noop); + const freshUser = await bot.users.fetch(userId, true, true).catch(noop); if (freshUser) { - bot.users.add(freshUser, bot); return freshUser; } @@ -1176,13 +1217,18 @@ export async function resolveUser(bot, value) { * Resolves a guild Member from the passed user id, user mention, or full username (with discriminator). * If the member is not found in the cache, it's fetched from the API. */ -export async function resolveMember(bot: Client, guild: Guild, value: string, fresh = false): Promise { +export async function resolveMember( + bot: Client, + guild: Guild, + value: string, + fresh = false, +): Promise { const userId = resolveUserId(bot, value); if (!userId) return null; // If we have the member cached, return that directly - if (guild.members.has(userId) && !fresh) { - return guild.members.get(userId) || null; + if (guild.members.cache.has(userId as Snowflake) && !fresh) { + return guild.members.cache.get(userId as Snowflake) || null; } // We don't want to spam the API by trying to fetch unknown members again and again, @@ -1192,9 +1238,9 @@ export async function resolveMember(bot: Client, guild: Guild, value: string, fr return null; } - const freshMember = await bot.getRESTGuildMember(guild.id, userId).catch(noop); + const freshMember = await guild.members.fetch({ user: userId as Snowflake, force: true }).catch(noop); if (freshMember) { - freshMember.id = userId; + // freshMember.id = userId; // I dont even know why this is here -Dark return freshMember; } @@ -1222,7 +1268,7 @@ export async function resolveRoleId(bot: Client, guildId: string, value: string) } // Role name - const roleList = await bot.getRESTGuildRoles(guildId); + const roleList = (await bot.guilds.fetch(guildId as Snowflake)).roles.cache; const role = roleList.filter(x => x.name.toLocaleLowerCase() === value.toLocaleLowerCase()); if (role[0]) { return role[0].id; @@ -1236,11 +1282,9 @@ export async function resolveRoleId(bot: Client, guildId: string, value: string) return null; } -const inviteCache = new SimpleCache | null>>(10 * MINUTES, 200); +const inviteCache = new SimpleCache>(10 * MINUTES, 200); -type ResolveInviteReturnType = Promise< - (T extends true ? Invite<"withCount" | "withMetadata"> : Invite<"withMetadata">) | null ->; +type ResolveInviteReturnType = Promise; export async function resolveInvite( client: Client, code: string, @@ -1252,32 +1296,44 @@ export async function resolveInvite( return inviteCache.get(key) as ResolveInviteReturnType; } - // @ts-ignore: the getInvite() withCounts typings are blergh - const promise = client.getInvite(code, withCounts).catch(() => null); + const promise = client.fetchInvite(code).catch(() => null); inviteCache.set(key, promise); return promise as ResolveInviteReturnType; } -export async function confirm(bot: Client, channel: TextableChannel, userId: string, content: MessageContent) { - const msg = await channel.createMessage(content); - const reply = await helpers.waitForReaction(bot, msg, ["✅", "❌"], userId); - msg.delete().catch(noop); - return reply && reply.name === "✅"; +const internalStickerCache: LimitedCollection = new LimitedCollection({ maxSize: 500 }); + +export async function resolveStickerId(bot: Client, id: Snowflake): Promise { + const cachedSticker = internalStickerCache.get(id); + if (cachedSticker) return cachedSticker; + + const fetchedSticker = await bot.fetchSticker(id).catch(() => null); + if (fetchedSticker) { + internalStickerCache.set(id, fetchedSticker); + } + + return fetchedSticker; +} + +export async function confirm(channel: TextChannel, userId: string, content: MessageOptions): Promise { + return waitForButtonConfirm(channel, content, { restrictToId: userId }); } export function messageSummary(msg: SavedMessage) { // Regular text content - let result = "```\n" + (msg.data.content ? disableCodeBlocks(msg.data.content) : "") + "```"; + let result = "```\n" + (msg.data.content ? Util.escapeCodeBlock(msg.data.content) : "") + "```"; // Rich embed - const richEmbed = (msg.data.embeds || []).find(e => (e as Embed).type === "rich"); - if (richEmbed) result += "Embed:```" + disableCodeBlocks(JSON.stringify(richEmbed)) + "```"; + const richEmbed = (msg.data.embeds || []).find(e => (e as MessageEmbed).type === "rich"); + if (richEmbed) result += "Embed:```" + Util.escapeCodeBlock(JSON.stringify(richEmbed)) + "```"; // Attachments if (msg.data.attachments) { result += - "Attachments:\n" + msg.data.attachments.map((a: Attachment) => disableLinkPreviews(a.url)).join("\n") + "\n"; + "Attachments:\n" + + msg.data.attachments.map((a: MessageAttachment) => disableLinkPreviews(a.url)).join("\n") + + "\n"; } return result; @@ -1285,23 +1341,23 @@ export function messageSummary(msg: SavedMessage) { export function verboseUserMention(user: User | UnknownUser): string { if (user.id == null) { - return `**${user.username}#${user.discriminator}**`; + return `**${user.tag}**`; } - return `<@!${user.id}> (**${user.username}#${user.discriminator}**, \`${user.id}\`)`; + return `<@!${user.id}> (**${user.tag}**, \`${user.id}\`)`; } export function verboseUserName(user: User | UnknownUser): string { if (user.id == null) { - return `**${user.username}#${user.discriminator}**`; + return `**${user.tag}**`; } - return `**${user.username}#${user.discriminator}** (\`${user.id}\`)`; + return `**${user.tag}** (\`${user.id}\`)`; } export function verboseChannelMention(channel: GuildChannel): string { const plainTextName = - channel.type === Constants.ChannelTypes.GUILD_VOICE || channel.type === Constants.ChannelTypes.GUILD_STAGE + channel.type === ChannelTypeStrings.VOICE || channel.type === ChannelTypeStrings.STAGE ? channel.name : `#${channel.name}`; return `<#${channel.id}> (**${plainTextName}**, \`${channel.id}\`)`; @@ -1396,8 +1452,8 @@ export function canUseEmoji(client: Client, emoji: string): boolean { if (isUnicodeEmoji(emoji)) { return true; } else if (isSnowflake(emoji)) { - for (const guild of client.guilds.values()) { - if (guild.emojis.some(e => (e as any).id === emoji)) { + for (const guild of client.guilds.cache) { + if (guild[1].emojis.cache.some(e => (e as any).id === emoji)) { return true; } } @@ -1420,19 +1476,19 @@ export function trimMultilineString(str) { } export const trimPluginDescription = trimMultilineString; -export function isFullMessage(msg: PossiblyUncachedMessage): msg is Message { +export function isFullMessage(msg: Message | PartialMessage): msg is Message { return (msg as Message).createdAt != null; } -export function isGuildInvite(invite: Invite): invite is GuildInvite { +export function isGuildInvite(invite: Invite): invite is GuildInvite { return invite.guild != null; } -export function isGroupDMInvite(invite: Invite): invite is GroupDMInvite { - return invite.guild == null && invite.channel?.type === Constants.ChannelTypes.GROUP_DM; +export function isGroupDMInvite(invite: Invite): invite is GroupDMInvite { + return invite.guild == null && invite.channel?.type === ChannelTypeStrings.GROUP; } -export function inviteHasCounts(invite: Invite): invite is Invite<"withCount"> { +export function inviteHasCounts(invite: Invite): invite is Invite { return invite.memberCount != null; } diff --git a/backend/src/utils/canAssignRole.ts b/backend/src/utils/canAssignRole.ts index f42778bd..a88a61e9 100644 --- a/backend/src/utils/canAssignRole.ts +++ b/backend/src/utils/canAssignRole.ts @@ -1,9 +1,9 @@ -import { Constants, Guild, Member, Role } from "eris"; +import { Guild, GuildMember, Permissions, Role, Snowflake } from "discord.js"; import { getMissingPermissions } from "./getMissingPermissions"; import { hasDiscordPermissions } from "./hasDiscordPermissions"; -export function canAssignRole(guild: Guild, member: Member, roleId: string) { - if (getMissingPermissions(member.permission, Constants.Permissions.manageRoles)) { +export function canAssignRole(guild: Guild, member: GuildMember, roleId: string) { + if (getMissingPermissions(member.permissions, Permissions.FLAGS.MANAGE_ROLES)) { return false; } @@ -11,14 +11,14 @@ export function canAssignRole(guild: Guild, member: Member, roleId: string) { return false; } - const targetRole = guild.roles.get(roleId); + const targetRole = guild.roles.cache.get(roleId as Snowflake); if (!targetRole) { return false; } - const memberRoles = member.roles.map(_roleId => guild.roles.get(_roleId)!); + const memberRoles = member.roles.cache; const highestRoleWithManageRoles = memberRoles.reduce((highest, role) => { - if (!hasDiscordPermissions(role.permissions, Constants.Permissions.manageRoles)) return highest; + if (!hasDiscordPermissions(role.permissions, Permissions.FLAGS.MANAGE_ROLES)) return highest; if (highest == null) return role; if (role.position > highest.position) return role; return highest; diff --git a/backend/src/utils/canReadChannel.ts b/backend/src/utils/canReadChannel.ts index fb14874a..94268c4b 100644 --- a/backend/src/utils/canReadChannel.ts +++ b/backend/src/utils/canReadChannel.ts @@ -1,8 +1,8 @@ -import { Constants, GuildChannel, Member } from "eris"; -import { readChannelPermissions } from "./readChannelPermissions"; +import { GuildChannel, GuildMember } from "discord.js"; import { getMissingChannelPermissions } from "./getMissingChannelPermissions"; +import { readChannelPermissions } from "./readChannelPermissions"; -export function canReadChannel(channel: GuildChannel, member: Member) { +export function canReadChannel(channel: GuildChannel, member: GuildMember) { // Not missing permissions required to read the channel = can read channel return !getMissingChannelPermissions(member, channel, readChannelPermissions); } diff --git a/backend/src/utils/configAccessibleObjects.ts b/backend/src/utils/configAccessibleObjects.ts new file mode 100644 index 00000000..9319fc08 --- /dev/null +++ b/backend/src/utils/configAccessibleObjects.ts @@ -0,0 +1,182 @@ +import { + Emoji, + GuildChannel, + GuildMember, + PartialGuildMember, + Role, + Snowflake, + StageInstance, + Sticker, + ThreadChannel, + User, +} from "discord.js"; +import { UnknownUser } from "src/utils"; + +export interface IConfigAccessibleUser { + id: Snowflake | string; + username: string; + discriminator: string; + mention: string; + tag: string; + avatarURL?: string; + bot?: boolean; + createdAt?: number; +} + +export interface IConfigAccessibleRole { + id: Snowflake; + name: string; + createdAt: number; + hexColor: string; + hoist: boolean; +} + +export interface IConfigAccessibleMember extends IConfigAccessibleUser { + user: IConfigAccessibleUser; + nick: string; + roles: IConfigAccessibleRole[]; + joinedAt?: number; + // guildAvatarURL: string, Once DJS supports per-server avatars + guildName: string; +} + +export function userToConfigAccessibleUser(user: User | UnknownUser): IConfigAccessibleUser { + if (user.tag === "Unknown#0000") { + const toReturnPartial: IConfigAccessibleUser = { + id: user.id, + username: "Unknown", + discriminator: "0000", + mention: `<@${user.id}>`, + tag: "Unknown#0000", + }; + + return toReturnPartial; + } + + const properUser = user as User; + const toReturn: IConfigAccessibleUser = { + id: properUser.id, + username: properUser.username, + discriminator: properUser.discriminator, + mention: `<@${properUser.id}>`, + tag: properUser.tag, + avatarURL: properUser.displayAvatarURL({ dynamic: true }), + bot: properUser.bot, + createdAt: properUser.createdTimestamp, + }; + + return toReturn; +} + +export function roleToConfigAccessibleRole(role: Role): IConfigAccessibleRole { + const toReturn: IConfigAccessibleRole = { + id: role.id, + name: role.name, + createdAt: role.createdTimestamp, + hexColor: role.hexColor, + hoist: role.hoist, + }; + + return toReturn; +} + +export function memberToConfigAccessibleMember(member: GuildMember | PartialGuildMember): IConfigAccessibleMember { + const user = userToConfigAccessibleUser(member.user!); + + const toReturn: IConfigAccessibleMember = { + ...user, + user, + nick: member.nickname ?? "*None*", + roles: [...member.roles.cache.mapValues(r => roleToConfigAccessibleRole(r)).values()], + joinedAt: member.joinedTimestamp ?? undefined, + guildName: member.guild.name, + }; + + return toReturn; +} + +export interface IConfigAccessibleChannel { + id: Snowflake; + name: string; + mention: string; + parentId?: Snowflake; +} + +export function channelToConfigAccessibleChannel(channel: GuildChannel | ThreadChannel): IConfigAccessibleChannel { + const toReturn: IConfigAccessibleChannel = { + id: channel.id, + name: channel.name, + mention: `<#${channel.id}>`, + parentId: channel.parentId ?? undefined, + }; + + return toReturn; +} + +export interface IConfigAccessibleStage { + channelId: Snowflake; + channelMention: string; + createdAt: number; + discoverable: boolean; + topic: string; +} + +export function stageToConfigAccessibleStage(stage: StageInstance): IConfigAccessibleStage { + const toReturn: IConfigAccessibleStage = { + channelId: stage.channelId, + channelMention: `<#${stage.channelId}>`, + createdAt: stage.createdTimestamp, + discoverable: !stage.discoverableDisabled, + topic: stage.topic, + }; + + return toReturn; +} + +export interface IConfigAccessibleEmoji { + id: Snowflake; + name: string; + createdAt?: number; + animated: boolean; + identifier: string; +} + +export function emojiToConfigAccessibleEmoji(emoji: Emoji): IConfigAccessibleEmoji { + const toReturn: IConfigAccessibleEmoji = { + id: emoji.id!, + name: emoji.name!, + createdAt: emoji.createdTimestamp ?? undefined, + animated: emoji.animated ?? false, + identifier: emoji.identifier, + }; + + return toReturn; +} + +export interface IConfigAccessibleSticker { + id: Snowflake; + guildId?: Snowflake; + packId?: Snowflake; + name: string; + description: string; + tags: string; + format: string; + animated: boolean; + url: string; +} + +export function stickerToConfigAccessibleSticker(sticker: Sticker): IConfigAccessibleSticker { + const toReturn: IConfigAccessibleSticker = { + id: sticker.id, + guildId: sticker.guildId ?? undefined, + packId: sticker.packId ?? undefined, + name: sticker.name, + description: sticker.description ?? "", + tags: sticker.tags?.join(", ") ?? "", + format: sticker.format, + animated: sticker.format === "PNG" ? false : true, + url: sticker.url, + }; + + return toReturn; +} diff --git a/backend/src/utils/createPaginatedMessage.ts b/backend/src/utils/createPaginatedMessage.ts index 76e55225..e711c620 100644 --- a/backend/src/utils/createPaginatedMessage.ts +++ b/backend/src/utils/createPaginatedMessage.ts @@ -1,9 +1,19 @@ -import { Client, Emoji, MemberPartial, Message, MessageContent, TextableChannel } from "eris"; +import { + Client, + Message, + MessageEditOptions, + MessageOptions, + MessageReaction, + PartialMessageReaction, + PartialUser, + TextChannel, + User, +} from "discord.js"; import { Awaitable } from "knub/dist/utils"; import { MINUTES, noop } from "../utils"; import Timeout = NodeJS.Timeout; -export type LoadPageFn = (page: number) => Awaitable; +export type LoadPageFn = (page: number) => Awaitable; export interface PaginateMessageOpts { timeout: number; @@ -17,19 +27,22 @@ const defaultOpts: PaginateMessageOpts = { export async function createPaginatedMessage( client: Client, - channel: TextableChannel, + channel: TextChannel | User, totalPages: number, loadPageFn: LoadPageFn, opts: Partial = {}, ): Promise { const fullOpts = { ...defaultOpts, ...opts } as PaginateMessageOpts; const firstPageContent = await loadPageFn(1); - const message = await channel.createMessage(firstPageContent); + const message = await channel.send(firstPageContent); let page = 1; let pageLoadId = 0; // Used to avoid race conditions when rapidly switching pages - const reactionListener = async (reactionMessage: Message, emoji: Emoji, reactor: MemberPartial) => { - if (reactionMessage.id !== message.id) { + const reactionListener = async ( + reactionMessage: MessageReaction | PartialMessageReaction, + reactor: User | PartialUser, + ) => { + if (reactionMessage.message.id !== message.id) { return; } @@ -37,14 +50,14 @@ export async function createPaginatedMessage( return; } - if (reactor.id === client.user.id) { + if (reactor.id === client.user!.id) { return; } let pageDelta = 0; - if (emoji.name === "⬅️") { + if (reactionMessage.emoji.name === "⬅️") { pageDelta = -1; - } else if (emoji.name === "➡️") { + } else if (reactionMessage.emoji.name === "➡️") { pageDelta = 1; } @@ -65,7 +78,7 @@ export async function createPaginatedMessage( } message.edit(newPageContent).catch(noop); - message.removeReaction(emoji.name, reactor.id); + reactionMessage.users.remove(reactor.id).catch(noop); refreshTimeout(); }; client.on("messageReactionAdd", reactionListener); @@ -76,7 +89,7 @@ export async function createPaginatedMessage( const refreshTimeout = () => { clearTimeout(timeout); timeout = setTimeout(() => { - message.removeReactions().catch(noop); + message.reactions.removeAll().catch(noop); client.off("messageReactionAdd", reactionListener); }, fullOpts.timeout); }; @@ -84,8 +97,8 @@ export async function createPaginatedMessage( refreshTimeout(); // Add reactions - message.addReaction("⬅️").catch(noop); - message.addReaction("➡️").catch(noop); + message.react("⬅️").catch(noop); + message.react("➡️").catch(noop); return message; } diff --git a/backend/src/utils/crypt.test.ts b/backend/src/utils/crypt.test.ts index 00619645..38b381ca 100644 --- a/backend/src/utils/crypt.test.ts +++ b/backend/src/utils/crypt.test.ts @@ -1,6 +1,5 @@ import test from "ava"; - -import { encrypt, decrypt } from "./crypt"; +import { decrypt, encrypt } from "./crypt"; test("encrypt() followed by decrypt()", t => { const original = "banana 123 👀 💕"; // Includes emojis to verify utf8 stuff works diff --git a/backend/src/utils/crypt.ts b/backend/src/utils/crypt.ts index 3184f328..fdf81109 100644 --- a/backend/src/utils/crypt.ts +++ b/backend/src/utils/crypt.ts @@ -1,7 +1,6 @@ +import crypto from "crypto"; import "../loadEnv"; -import crypto, { DecipherGCM } from "crypto"; - if (!process.env.KEY) { // tslint:disable-next-line:no-console console.error("Environment value KEY required for encryption"); diff --git a/backend/src/utils/erisAllowedMentionsToDjsMentionOptions.ts b/backend/src/utils/erisAllowedMentionsToDjsMentionOptions.ts new file mode 100644 index 00000000..54f9649b --- /dev/null +++ b/backend/src/utils/erisAllowedMentionsToDjsMentionOptions.ts @@ -0,0 +1,43 @@ +import { MessageMentionOptions, MessageMentionTypes, Snowflake } from "discord.js"; + +export function erisAllowedMentionsToDjsMentionOptions( + allowedMentions: ErisAllowedMentionFormat | undefined, +): MessageMentionOptions | undefined { + if (allowedMentions === undefined) return undefined; + + const parse: MessageMentionTypes[] = []; + let users: Snowflake[] | undefined; + let roles: Snowflake[] | undefined; + + if (Array.isArray(allowedMentions.users)) { + users = allowedMentions.users as Snowflake[]; + } else if (allowedMentions.users === true) { + parse.push("users"); + } + + if (Array.isArray(allowedMentions.roles)) { + roles = allowedMentions.roles as Snowflake[]; + } else if (allowedMentions.roles === true) { + parse.push("roles"); + } + + if (allowedMentions.everyone === true) { + parse.push("everyone"); + } + + const mentions: MessageMentionOptions = { + parse, + users, + roles, + repliedUser: allowedMentions.repliedUser, + }; + + return mentions; +} + +export interface ErisAllowedMentionFormat { + everyone?: boolean | undefined; + users?: boolean | string[] | undefined; + roles?: boolean | string[] | undefined; + repliedUser?: boolean | undefined; +} diff --git a/backend/src/utils/getChunkedEmbedFields.ts b/backend/src/utils/getChunkedEmbedFields.ts index 7b692164..0568e840 100644 --- a/backend/src/utils/getChunkedEmbedFields.ts +++ b/backend/src/utils/getChunkedEmbedFields.ts @@ -1,4 +1,4 @@ -import { EmbedField } from "eris"; +import { EmbedField } from "discord.js"; import { chunkMessageLines, emptyEmbedValue } from "../utils"; export function getChunkedEmbedFields(name: string, value: string, inline?: boolean): EmbedField[] { @@ -10,11 +10,13 @@ export function getChunkedEmbedFields(name: string, value: string, inline?: bool fields.push({ name, value: chunks[i], + inline: false, }); } else { fields.push({ name: emptyEmbedValue, value: chunks[i], + inline: false, }); } } diff --git a/backend/src/utils/getMissingChannelPermissions.ts b/backend/src/utils/getMissingChannelPermissions.ts index 4701ddb4..a1a7be9a 100644 --- a/backend/src/utils/getMissingChannelPermissions.ts +++ b/backend/src/utils/getMissingChannelPermissions.ts @@ -1,4 +1,4 @@ -import { Constants, GuildChannel, Member, Permission } from "eris"; +import { GuildChannel, GuildMember, ThreadChannel } from "discord.js"; import { getMissingPermissions } from "./getMissingPermissions"; /** @@ -6,10 +6,11 @@ import { getMissingPermissions } from "./getMissingPermissions"; * @return Bitmask of missing permissions */ export function getMissingChannelPermissions( - member: Member, - channel: GuildChannel, + member: GuildMember, + channel: GuildChannel | ThreadChannel, requiredPermissions: number | bigint, ): bigint { - const memberChannelPermissions = channel.permissionsOf(member.id); + const memberChannelPermissions = channel.permissionsFor(member.id); + if (!memberChannelPermissions) return BigInt(requiredPermissions); return getMissingPermissions(memberChannelPermissions, requiredPermissions); } diff --git a/backend/src/utils/getMissingPermissions.ts b/backend/src/utils/getMissingPermissions.ts index 9dd6881a..3b1adf45 100644 --- a/backend/src/utils/getMissingPermissions.ts +++ b/backend/src/utils/getMissingPermissions.ts @@ -1,17 +1,20 @@ -import { Constants, Permission } from "eris"; +import { Permissions } from "discord.js"; /** - * @param resolvedPermissions A Permission object from e.g. GuildChannel#permissionsOf() or Member#permission + * @param resolvedPermissions A Permission object from e.g. GuildChannel#permissionsFor() or Member#permission * @param requiredPermissions Bitmask of required permissions * @return Bitmask of missing permissions */ -export function getMissingPermissions(resolvedPermissions: Permission, requiredPermissions: number | bigint): bigint { - const allowedPermissions = BigInt(resolvedPermissions.allow); - const nRequiredPermissions = BigInt(requiredPermissions); +export function getMissingPermissions( + resolvedPermissions: Permissions | Readonly, + requiredPermissions: number | bigint, +): bigint { + const allowedPermissions = resolvedPermissions; + const nRequiredPermissions = requiredPermissions; - if (Boolean(allowedPermissions & BigInt(Constants.Permissions.administrator))) { + if (Boolean(allowedPermissions.bitfield & Permissions.FLAGS.ADMINISTRATOR)) { return BigInt(0); } - return nRequiredPermissions & ~allowedPermissions; + return BigInt(nRequiredPermissions) & ~allowedPermissions.bitfield; } diff --git a/backend/src/utils/getPermissionNames.ts b/backend/src/utils/getPermissionNames.ts index 48e057bc..bc05ee64 100644 --- a/backend/src/utils/getPermissionNames.ts +++ b/backend/src/utils/getPermissionNames.ts @@ -1,18 +1,11 @@ -import { Constants } from "eris"; - -const camelCaseToTitleCase = str => - str - .replace(/([a-z])([A-Z])/g, "$1 $2") - .split(" ") - .map(w => w[0].toUpperCase() + w.slice(1)) - .join(" "); +import { Permissions } from "discord.js"; const permissionNumberToName: Map = new Map(); const ignoredPermissionConstants = ["all", "allGuild", "allText", "allVoice"]; -for (const key in Constants.Permissions) { +for (const key in Permissions.FLAGS) { if (ignoredPermissionConstants.includes(key)) continue; - permissionNumberToName.set(BigInt(Constants.Permissions[key]), camelCaseToTitleCase(key)); + permissionNumberToName.set(BigInt(Permissions.FLAGS[key]), key); } /** diff --git a/backend/src/utils/hasDiscordPermissions.ts b/backend/src/utils/hasDiscordPermissions.ts index f4aefcab..c8f4823a 100644 --- a/backend/src/utils/hasDiscordPermissions.ts +++ b/backend/src/utils/hasDiscordPermissions.ts @@ -1,16 +1,19 @@ -import { Constants, Permission } from "eris"; +import { Permissions } from "discord.js"; /** * @param resolvedPermissions A Permission object from e.g. GuildChannel#permissionsOf() or Member#permission * @param requiredPermissions Bitmask of required permissions */ -export function hasDiscordPermissions(resolvedPermissions: Permission, requiredPermissions: number | bigint) { - const allowedPermissions = BigInt(resolvedPermissions.allow); - const nRequiredPermissions = BigInt(requiredPermissions); +export function hasDiscordPermissions( + resolvedPermissions: Permissions | Readonly | null, + requiredPermissions: number | bigint, +) { + const allowedPermissions = resolvedPermissions; + const nRequiredPermissions = requiredPermissions; - if (Boolean(allowedPermissions & BigInt(Constants.Permissions.administrator))) { + if (Boolean(allowedPermissions?.bitfield! & Permissions.FLAGS.ADMINISTRATOR)) { return true; } - return Boolean((allowedPermissions & nRequiredPermissions) === nRequiredPermissions); + return Boolean((allowedPermissions?.bitfield! & BigInt(nRequiredPermissions)) === nRequiredPermissions); } diff --git a/backend/src/utils/idToTimestamp.ts b/backend/src/utils/idToTimestamp.ts new file mode 100644 index 00000000..10b964a5 --- /dev/null +++ b/backend/src/utils/idToTimestamp.ts @@ -0,0 +1,6 @@ +import { Snowflake, SnowflakeUtil } from "discord.js"; + +export function idToTimestamp(id: string) { + if (typeof id === "number") return null; + return SnowflakeUtil.deconstruct(id as Snowflake).timestamp; +} diff --git a/backend/src/utils/isDefaultSticker.ts b/backend/src/utils/isDefaultSticker.ts new file mode 100644 index 00000000..85e3e974 --- /dev/null +++ b/backend/src/utils/isDefaultSticker.ts @@ -0,0 +1,306 @@ +const defaultStickerIds = [ + "749044136589393960", + "749045492352155769", + "749045743976710154", + "749046077629399122", + "749046696482439188", + "749047112028651530", + "749049128012742676", + "749051158542417980", + "749051341325729913", + "749051517964648458", + "749051844663181383", + "749052011751932006", + "749052505308266645", + "749052707536371812", + "749052944682582036", + "749053210760577245", + "749053441527251087", + "749053689419006003", + "749053927907131433", + "749054120345993216", + "749054292937277450", + "749054660769218631", + "749054894585151518", + "749055120263872532", + "754112474868875294", + "755244355563815073", + "755244428305760266", + "755244598799892490", + "755244649655959615", + "755490897143136446", + "781291131828699156", + "781291442961383434", + "781291606493495306", + "781321379546398740", + "781321702805340200", + "781321874650562560", + "781321970301796372", + "781322427820277791", + "781322566060343296", + "781322673527193620", + "781322765641973770", + "781322967127818240", + "781323072102858782", + "781323157239103548", + "781323249505927198", + "781323366921404426", + "781323471249604648", + "781323560756707328", + "781323628267962408", + "781323712640974858", + "781323769960202280", + "781323880723251220", + "781324010952458270", + "781324114685329417", + "781324245468315668", + "781324376884248596", + "781324451014246460", + "781324562905432064", + "781324642736144424", + "781324722394103808", + "813950454420471818", + "813950661292064808", + "813950759296172092", + "813950952436531213", + "813951067557462106", + "813951129544818708", + "813951478803857408", + "813951723822645278", + "813951924604895242", + "813952523408113694", + "813952588650381332", + "813952646083772486", + "813952751200763934", + "813952825520291902", + "813952903064584202", + "809207198856904764", + "809207265092698112", + "809207315822936064", + "809207399054442526", + "809207795999572038", + "809207857773936710", + "809207919115239525", + "809208197235343410", + "809208263987953694", + "809208353884471376", + "809208424419426344", + "809208728251138088", + "809208771834019850", + "809209078261874688", + "809209146846871562", + "809209216966852628", + "809209266556764241", + "809209320494333952", + "809209482902503444", + "809209627450671114", + "809209856321650698", + "809209923765272586", + "809210027524620328", + "809210129978228766", + "809210201033932891", + "809210344311619584", + "809210578433736724", + "809210750702583868", + "809210904263917618", + "809211336633614346", + "818596923887583302", + "818596976521248819", + "818597244017049652", + "818597355619483688", + "818597454483161098", + "818597555608092722", + "818597623397220362", + "818597707132043285", + "818597810047680532", + "818597885671243776", + "818598022798770186", + "818598125077266432", + "818598371324592218", + "818598476883165194", + "818599312882794506", + "754104467573571584", + "754106820079124480", + "754107009720385556", + "754107496884338698", + "754107539200671765", + "754107634172297306", + "754108691493683221", + "754108771852222564", + "754108811354046554", + "754108835895181322", + "754108890559283200", + "754108923509997568", + "754108948356792320", + "754108992195919903", + "754109038693974057", + "754109076933443614", + "754109137830281297", + "754109419821727885", + "754109474691612782", + "754109519113617478", + "754109542526091434", + "754109580069437481", + "754109748999225374", + "754109772449710080", + "754109815877402634", + "754109869325549638", + "754109908995276810", + "754109937872928857", + "754109983108497468", + "754110021574328400", + "823973720266899506", + "823973812748025937", + "823974092700254238", + "823974203929526292", + "823974288897343518", + "823974429834477578", + "823974530686648440", + "823974669748666418", + "823974764057722910", + "823974837156446208", + "823974930399232020", + "823975146263412786", + "823976022025306152", + "823976102976290866", + "823976251269054494", + "751604756748959874", + "751605093065031760", + "751605170818777108", + "751605236476543086", + "751605353585836101", + "751605453842022562", + "751605541687787550", + "751605606070091887", + "751605670654246972", + "751605738270359592", + "751605803932319754", + "751605873083678802", + "751605941375598672", + "751606014054236261", + "751606065447305216", + "751606120493350982", + "751606190383038604", + "751606254073544784", + "751606317936017458", + "751606379340365864", + "751606441315401848", + "751606491542192200", + "751606539600527410", + "751606636698927157", + "751606719611928586", + "751606808837357608", + "751606868115193948", + "751606917494734959", + "751606992849862706", + "751607061762277396", + "819128604311027752", + "819129296374595614", + "819130301702995968", + "819131032259133440", + "819131232642007062", + "819131655738228796", + "819131835635466280", + "819131978401316864", + "819132831023628298", + "819139462373310494", + "819139728128344064", + "819140386551365642", + "819140940018352178", + "819141435474706472", + "819145601031733269", + "772963467622744075", + "772963523630071828", + "772963562553081886", + "772970847232458782", + "772972089963577354", + "772973760457605182", + "772974139053047829", + "772974519786799114", + "772975031487168582", + "772975484874522674", + "772975929998835722", + "772976230718111764", + "772976562831097906", + "772976718939160606", + "772976899152543745", + "773897032485175296", + "773898515990315028", + "773899933131604008", + "773901313578369044", + "773902656442728488", + "773903221272870973", + "773903633951490098", + "773904449440579624", + "773909554005540894", + "773911171987144754", + "773912319839043625", + "773912616425881630", + "773914273075429387", + "773914595756081172", + "773914892800622612", + "776241800930787349", + "776241838813216788", + "776241877862187048", + "776241909374910534", + "776242510334525460", + "776242536820899861", + "776242590148198400", + "776242614663512095", + "776242643717455882", + "776242672562077696", + "776242703834284132", + "776242899662405712", + "776242924542492672", + "776242962072993792", + "776243107654008872", + "776243140750606366", + "776243172258611200", + "776243957259698226", + "776243988038025287", + "776244033244627004", + "776244136725970974", + "776244212806713344", + "776244240753098752", + "776244473184256000", + "776244492948209674", + "776244520928542731", + "776244545096122379", + "776244577509179412", + "776244610917335070", + "776244640239452200", + "816086581509095424", + "816086770541396028", + "816086882823831613", + "816086934266839040", + "816087074310193162", + "816087132447178774", + "816087220753924096", + "816087273548415006", + "816087483640709131", + "816087668630618152", + "816087792291282944", + "816087883252760617", + "816087973053464606", + "816088051121258596", + "816088135334494267", + "831569193391489054", + "831569335696228352", + "831569414746144798", + "831569534459576381", + "831569719814914108", + "831569909288140832", + "831570117057970176", + "831570479415689309", + "831570715471380550", + "831571053569769492", + "831571151535996988", + "831571320377835540", + "831571470824112138", + "831571594055778304", + "831571726223540294", +]; + +export function isDefaultSticker(id: string): boolean { + return defaultStickerIds.includes(id); +} diff --git a/backend/src/utils/lockNameHelpers.ts b/backend/src/utils/lockNameHelpers.ts index 4a16bd33..81f3bc04 100644 --- a/backend/src/utils/lockNameHelpers.ts +++ b/backend/src/utils/lockNameHelpers.ts @@ -1,11 +1,11 @@ -import { Member, Message, User } from "eris"; +import { GuildMember, Message, User } from "discord.js"; import { SavedMessage } from "../data/entities/SavedMessage"; export function allStarboardsLock() { return `starboards`; } -export function banLock(user: Member | User | { id: string }) { +export function banLock(user: GuildMember | User | { id: string }) { return `ban-${user.id}`; } @@ -13,7 +13,7 @@ export function counterIdLock(counterId: number | string) { return `counter-${counterId}`; } -export function memberRolesLock(member: Member | User | { id: string }) { +export function memberRolesLock(member: GuildMember | User | { id: string }) { return `member-roles-${member.id}`; } @@ -21,6 +21,6 @@ export function messageLock(message: Message | SavedMessage | { id: string }) { return `message-${message.id}`; } -export function muteLock(user: Member | User | { id: string }) { +export function muteLock(user: GuildMember | User | { id: string }) { return `mute-${user.id}`; } diff --git a/backend/src/utils/messageHasContent.ts b/backend/src/utils/messageHasContent.ts index ca3dd8bf..b70918be 100644 --- a/backend/src/utils/messageHasContent.ts +++ b/backend/src/utils/messageHasContent.ts @@ -1,4 +1,4 @@ -import { MessageContent } from "eris"; +import { MessageOptions } from "discord.js"; function embedHasContent(embed: any) { for (const [key, value] of Object.entries(embed)) { @@ -18,7 +18,7 @@ function embedHasContent(embed: any) { return false; } -export function messageHasContent(content: MessageContent): boolean { +export function messageHasContent(content: string | MessageOptions): boolean { if (typeof content === "string") { return content.trim() !== ""; } @@ -27,8 +27,12 @@ export function messageHasContent(content: MessageContent): boolean { return true; } - if (content.embed && embedHasContent(content.embed)) { - return true; + if (content.embeds) { + for (const embed of content.embeds) { + if (embed && embedHasContent(embed)) { + return true; + } + } } return false; diff --git a/backend/src/utils/messageIsEmpty.ts b/backend/src/utils/messageIsEmpty.ts index 2cd3f255..d1b1f117 100644 --- a/backend/src/utils/messageIsEmpty.ts +++ b/backend/src/utils/messageIsEmpty.ts @@ -1,6 +1,6 @@ -import { MessageContent } from "eris"; +import { MessageOptions } from "discord.js"; import { messageHasContent } from "./messageHasContent"; -export function messageIsEmpty(content: MessageContent): boolean { +export function messageIsEmpty(content: string | MessageOptions): boolean { return !messageHasContent(content); } diff --git a/backend/src/utils/parseFuzzyTimezone.ts b/backend/src/utils/parseFuzzyTimezone.ts index 05d3b1c7..ef570e26 100644 --- a/backend/src/utils/parseFuzzyTimezone.ts +++ b/backend/src/utils/parseFuzzyTimezone.ts @@ -1,5 +1,5 @@ -import moment from "moment-timezone"; import escapeStringRegexp from "escape-string-regexp"; +import moment from "moment-timezone"; const normalizeTzName = str => str.replace(/[^a-zA-Z0-9+\-]/g, "").toLowerCase(); diff --git a/backend/src/utils/readChannelPermissions.ts b/backend/src/utils/readChannelPermissions.ts index fdfb4aac..16dae413 100644 --- a/backend/src/utils/readChannelPermissions.ts +++ b/backend/src/utils/readChannelPermissions.ts @@ -1,9 +1,9 @@ -import { Constants } from "eris"; +import { Permissions } from "discord.js"; /** * Bitmask of permissions required to read messages in a channel */ -export const readChannelPermissions = Constants.Permissions.readMessages | Constants.Permissions.readMessageHistory; +export const readChannelPermissions = Permissions.FLAGS.VIEW_CHANNEL | Permissions.FLAGS.READ_MESSAGE_HISTORY; /** * Bitmask of permissions required to read messages in a channel (bigint) diff --git a/backend/src/utils/resolveMessageTarget.ts b/backend/src/utils/resolveMessageTarget.ts index 4e06c52a..a7c3043c 100644 --- a/backend/src/utils/resolveMessageTarget.ts +++ b/backend/src/utils/resolveMessageTarget.ts @@ -1,7 +1,7 @@ -import { disableInlineCode, isSnowflake } from "../utils"; +import { Snowflake, TextChannel } from "discord.js"; +import { GuildPluginData } from "knub"; import { getChannelIdFromMessageId } from "../data/getChannelIdFromMessageId"; -import { GuildPluginData, TypeConversionError } from "knub"; -import { TextChannel } from "eris"; +import { isSnowflake } from "../utils"; const channelAndMessageIdRegex = /^(\d+)[\-\/](\d+)$/; const messageLinkRegex = /^https:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/\d+\/(\d+)\/(\d+)$/i; @@ -46,7 +46,7 @@ export async function resolveMessageTarget(pluginData: GuildPluginData, val return null; } - const channel = pluginData.guild.channels.get(result.channelId); + const channel = pluginData.guild.channels.resolve(result.channelId as Snowflake); if (!channel || !(channel instanceof TextChannel)) { return null; } diff --git a/backend/src/utils/safeFindRelevantAuditLogEntry.ts b/backend/src/utils/safeFindRelevantAuditLogEntry.ts index ab196cf0..8845e52e 100644 --- a/backend/src/utils/safeFindRelevantAuditLogEntry.ts +++ b/backend/src/utils/safeFindRelevantAuditLogEntry.ts @@ -1,7 +1,7 @@ import { GuildPluginData } from "knub"; -import { LogsPlugin } from "../plugins/Logs/LogsPlugin"; -import { findRelevantAuditLogEntry, isDiscordRESTError } from "../utils"; import { LogType } from "../data/LogType"; +import { LogsPlugin } from "../plugins/Logs/LogsPlugin"; +import { findRelevantAuditLogEntry, isDiscordAPIError } from "../utils"; /** * Wrapper for findRelevantAuditLogEntry() that handles permission errors gracefully. @@ -17,7 +17,7 @@ export async function safeFindRelevantAuditLogEntry( try { return await findRelevantAuditLogEntry(pluginData.guild, actionType, userId, attempts, attemptDelay); } catch (e) { - if (isDiscordRESTError(e) && e.code === 50013) { + if (isDiscordAPIError(e) && e.code === 50013) { const logs = pluginData.getPlugin(LogsPlugin); logs.log(LogType.BOT_ALERT, { body: "Missing permissions to read audit log", diff --git a/backend/src/utils/sendDM.ts b/backend/src/utils/sendDM.ts index 8e8261bf..7cfc3f87 100644 --- a/backend/src/utils/sendDM.ts +++ b/backend/src/utils/sendDM.ts @@ -1,6 +1,6 @@ -import { MessageContent, MessageFile, User } from "eris"; -import { createChunkedMessage, HOURS, isDiscordRESTError } from "../utils"; +import { MessagePayload, User } from "discord.js"; import { logger } from "../logger"; +import { createChunkedMessage, HOURS, isDiscordAPIError } from "../utils"; import Timeout = NodeJS.Timeout; let dmsDisabled = false; @@ -16,7 +16,7 @@ export class DMError extends Error {} const error20026 = "The bot cannot currently send DMs"; -export async function sendDM(user: User, content: MessageContent, source: string) { +export async function sendDM(user: User, content: string | MessagePayload, source: string) { if (dmsDisabled) { throw new DMError(error20026); } @@ -24,18 +24,13 @@ export async function sendDM(user: User, content: MessageContent, source: string logger.debug(`Sending ${source} DM to ${user.id}`); try { - const dmChannel = await user.getDMChannel(); - if (!dmChannel) { - throw new DMError("Unable to open DM channel"); - } - if (typeof content === "string") { - await createChunkedMessage(dmChannel, content); + await createChunkedMessage(user, content); } else { - await dmChannel.createMessage(content); + await user.send(content); } } catch (e) { - if (isDiscordRESTError(e) && e.code === 20026) { + if (isDiscordAPIError(e) && e.code === 20026) { logger.warn(`Received error code 20026: ${e.message}`); logger.warn("Disabling attempts to send DMs for 1 hour"); disableDMs(1 * HOURS); diff --git a/backend/src/utils/tColor.ts b/backend/src/utils/tColor.ts index df61f579..ccc4f6a3 100644 --- a/backend/src/utils/tColor.ts +++ b/backend/src/utils/tColor.ts @@ -1,9 +1,8 @@ -import * as t from "io-ts"; import { either } from "fp-ts/lib/Either"; -import { convertDelayStringToMS } from "../utils"; +import * as t from "io-ts"; +import { intToRgb } from "./intToRgb"; import { parseColor } from "./parseColor"; import { rgbToInt } from "./rgbToInt"; -import { intToRgb } from "./intToRgb"; export const tColor = new t.Type( "tColor", diff --git a/backend/src/utils/tValidTimezone.ts b/backend/src/utils/tValidTimezone.ts index 9922bd96..43c61c3e 100644 --- a/backend/src/utils/tValidTimezone.ts +++ b/backend/src/utils/tValidTimezone.ts @@ -1,5 +1,5 @@ -import * as t from "io-ts"; import { either } from "fp-ts/lib/Either"; +import * as t from "io-ts"; import { isValidTimezone } from "./isValidTimezone"; export const tValidTimezone = new t.Type( diff --git a/backend/src/utils/waitForInteraction.ts b/backend/src/utils/waitForInteraction.ts new file mode 100644 index 00000000..afc3641f --- /dev/null +++ b/backend/src/utils/waitForInteraction.ts @@ -0,0 +1,51 @@ +import { MessageActionRow, MessageButton, MessageComponentInteraction, MessageOptions, TextChannel } from "discord.js"; +import { noop } from "knub/dist/utils"; +import moment from "moment"; + +export async function waitForButtonConfirm( + channel: TextChannel, + toPost: MessageOptions, + options?: WaitForOptions, +): Promise { + return new Promise(async resolve => { + const idMod = `${channel.guild.id}-${moment.utc().valueOf()}`; + const row = new MessageActionRow().addComponents([ + new MessageButton() + .setStyle("SUCCESS") + .setLabel(options?.confirmText || "Confirm") + .setCustomId(`confirmButton:${idMod}`), + + new MessageButton() + .setStyle("DANGER") + .setLabel(options?.cancelText || "Cancel") + .setCustomId(`cancelButton:${idMod}`), + ]); + const message = await channel.send({ ...toPost, components: [row] }); + + const collector = message.createMessageComponentCollector({ time: 10000 }); + + collector.on("collect", (interaction: MessageComponentInteraction) => { + if (options?.restrictToId && options.restrictToId !== interaction.user.id) { + interaction.reply({ content: `You are not permitted to use these buttons.`, ephemeral: true }); + } else { + if (interaction.customId === `confirmButton:${idMod}`) { + message.delete(); + resolve(true); + } else if (interaction.customId === `cancelButton:${idMod}`) { + message.delete(); + resolve(false); + } + } + }); + collector.on("end", () => { + if (!message.deleted) message.delete().catch(noop); + resolve(false); + }); + }); +} + +export interface WaitForOptions { + restrictToId?: string; + confirmText?: string; + cancelText?: string; +} diff --git a/backend/src/validation.test.ts b/backend/src/validation.test.ts index e1019a85..795e67ff 100644 --- a/backend/src/validation.test.ts +++ b/backend/src/validation.test.ts @@ -1,7 +1,7 @@ -import { tDeepPartial } from "./utils"; -import * as t from "io-ts"; -import * as validatorUtils from "./validatorUtils"; import test from "ava"; +import * as t from "io-ts"; +import { tDeepPartial } from "./utils"; +import * as validatorUtils from "./validatorUtils"; test("tDeepPartial works", ava => { const originalSchema = t.type({ diff --git a/backend/src/validatorUtils.ts b/backend/src/validatorUtils.ts index afc1297f..8a6863f9 100644 --- a/backend/src/validatorUtils.ts +++ b/backend/src/validatorUtils.ts @@ -1,9 +1,8 @@ -import * as t from "io-ts"; -import { pipe } from "fp-ts/lib/pipeable"; -import { either, fold } from "fp-ts/lib/Either"; -import { noop } from "./utils"; import deepDiff from "deep-diff"; -import safeRegex from "safe-regex"; +import { either, fold } from "fp-ts/lib/Either"; +import { pipe } from "fp-ts/lib/pipeable"; +import * as t from "io-ts"; +import { noop } from "./utils"; const regexWithFlags = /^\/(.*?)\/([i]*)$/; @@ -123,7 +122,7 @@ export function decodeAndValidateStrict( err => report(validationResult), result => { // Make sure there are no extra properties - if (debug) + if (debug) { console.log( "JSON.stringify() check:", JSON.stringify(value) === JSON.stringify(result) @@ -131,6 +130,7 @@ export function decodeAndValidateStrict( : "they are not the same, might have excess", result, ); + } if (JSON.stringify(value) !== JSON.stringify(result)) { const diff = deepDiff(result, value); const errors = diff.filter(d => d.kind === "N").map(d => `Unknown property <${d.path.join(".")}>`); diff --git a/backend/src/yamlParseTest.ts b/backend/src/yamlParseTest.ts index dabb3727..2838591c 100644 --- a/backend/src/yamlParseTest.ts +++ b/backend/src/yamlParseTest.ts @@ -1,5 +1,5 @@ -import YAML from "yawn-yaml/cjs"; import { load } from "js-yaml"; +import YAML from "yawn-yaml/cjs"; const src = ` prefix: '!' diff --git a/package-lock.json b/package-lock.json index b6991f05..21898d07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,2077 @@ { "name": "@zeppelin/zeppelin", "version": "0.0.1", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "@zeppelin/zeppelin", + "version": "0.0.1", + "devDependencies": { + "husky": "^3.0.9", + "lint-staged": "^9.4.2", + "prettier": "^1.19.1", + "tslint": "^5.13.1", + "tslint-config-prettier": "^1.18.0", + "typescript": "^4.3.4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.0.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "dev": true, + "dependencies": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@samverschueren/stream-to-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", + "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", + "dev": true, + "dependencies": { + "any-observable": "^0.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "node_modules/@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "dependencies": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "12.12.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.5.tgz", + "integrity": "sha512-KEjODidV4XYUlJBF3XdjSH5FWoMCtO0utnhtdLf1AgeuZLOrRbvmU/gaRCVg7ZaQDjVf3l84egiY0mRNe5xE4A==", + "dev": true + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, + "node_modules/aggregate-error": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", + "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/any-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", + "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "dependencies": { + "callsites": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "dependencies": { + "caller-callsite": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "dependencies": { + "restore-cursor": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-truncate": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", + "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", + "dev": true, + "dependencies": { + "slice-ansi": "0.0.4", + "string-width": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/date-fns": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", + "dev": true + }, + "node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true + }, + "node_modules/del": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/del/-/del-5.1.0.tgz", + "integrity": "sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==", + "dev": true, + "dependencies": { + "globby": "^10.0.1", + "graceful-fs": "^4.2.2", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.1", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/elegant-spinner": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", + "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.0.tgz", + "integrity": "sha512-TrUz3THiq2Vy3bjfQUB2wNyPdGBeGmdjbzzBLhfHN4YFurYptCKwGq/TfiRavbGywFRzY6U2CdmQ1zmsY5yYaw==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fastq": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz", + "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==", + "dev": true, + "dependencies": { + "reusify": "^1.0.0" + } + }, + "node_modules/figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.1.tgz", + "integrity": "sha512-09/VS4iek66Dh2bctjRkowueRJbY1JDGR1L/zRxO1Qk8Uxs6PnqaNSqalpizPT+CDjre3hnEsuzvhgomz9qYrA==", + "dev": true + }, + "node_modules/get-stdin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", + "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/glob": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz", + "integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", + "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "dev": true, + "dependencies": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", + "dev": true + }, + "node_modules/husky": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/husky/-/husky-3.0.9.tgz", + "integrity": "sha512-Yolhupm7le2/MqC1VYLk/cNmYxsSsqKkTyBhzQHhPK1jFnC89mmmNVuGtLNabjDI6Aj8UNIr0KpRNuBkiC4+sg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "chalk": "^2.4.2", + "ci-info": "^2.0.0", + "cosmiconfig": "^5.2.1", + "execa": "^1.0.0", + "get-stdin": "^7.0.0", + "opencollective-postinstall": "^2.0.2", + "pkg-dir": "^4.2.0", + "please-upgrade-node": "^3.2.0", + "read-pkg": "^5.2.0", + "run-node": "^1.0.0", + "slash": "^3.0.0" + }, + "bin": { + "husky-run": "run.js", + "husky-upgrade": "lib/upgrader/bin.js" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-observable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", + "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", + "dev": true, + "dependencies": { + "symbol-observable": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", + "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "node_modules/lint-staged": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-9.4.2.tgz", + "integrity": "sha512-OFyGokJSWTn2M6vngnlLXjaHhi8n83VIZZ5/1Z26SULRUWgR3ITWpAEQC9Pnm3MC/EpCxlwts/mQWDHNji2+zA==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2", + "commander": "^2.20.0", + "cosmiconfig": "^5.2.1", + "debug": "^4.1.1", + "dedent": "^0.7.0", + "del": "^5.0.0", + "execa": "^2.0.3", + "listr": "^0.14.3", + "log-symbols": "^3.0.0", + "micromatch": "^4.0.2", + "normalize-path": "^3.0.0", + "please-upgrade-node": "^3.1.1", + "string-argv": "^0.3.0", + "stringify-object": "^3.3.0" + }, + "bin": { + "lint-staged": "bin/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/cross-spawn": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", + "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/lint-staged/node_modules/execa": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-2.1.0.tgz", + "integrity": "sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^3.0.0", + "onetime": "^5.1.0", + "p-finally": "^2.0.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": "^8.12.0 || >=9.7.0" + } + }, + "node_modules/lint-staged/node_modules/get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lint-staged/node_modules/is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/lint-staged/node_modules/npm-run-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz", + "integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lint-staged/node_modules/p-finally": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", + "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/lint-staged/node_modules/path-key": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.0.tgz", + "integrity": "sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/lint-staged/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lint-staged/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/lint-staged/node_modules/which": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.1.tgz", + "integrity": "sha512-N7GBZOTswtB9lkQBZA4+zAXrjEIWAUOB93AvzUiudRzRxhUdLURQ7D/gAIMY1gatT/LTbmbcv8SiYazy3eYB7w==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/listr": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", + "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", + "dev": true, + "dependencies": { + "@samverschueren/stream-to-observable": "^0.3.0", + "is-observable": "^1.1.0", + "is-promise": "^2.1.0", + "is-stream": "^1.1.0", + "listr-silent-renderer": "^1.1.1", + "listr-update-renderer": "^0.5.0", + "listr-verbose-renderer": "^0.5.0", + "p-map": "^2.0.0", + "rxjs": "^6.3.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/listr-silent-renderer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", + "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/listr-update-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", + "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", + "dev": true, + "dependencies": { + "chalk": "^1.1.3", + "cli-truncate": "^0.2.1", + "elegant-spinner": "^1.0.1", + "figures": "^1.7.0", + "indent-string": "^3.0.0", + "log-symbols": "^1.0.2", + "log-update": "^2.3.0", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "listr": "^0.14.2" + } + }, + "node_modules/listr-update-renderer/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/listr-update-renderer/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/listr-update-renderer/node_modules/indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/listr-update-renderer/node_modules/log-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", + "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "dev": true, + "dependencies": { + "chalk": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/listr-update-renderer/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/listr-verbose-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", + "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "cli-cursor": "^2.1.0", + "date-fns": "^1.27.2", + "figures": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/listr-verbose-renderer/node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/listr/node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", + "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", + "dev": true, + "dependencies": { + "ansi-escapes": "^3.0.0", + "cli-cursor": "^2.0.0", + "wrap-ansi": "^3.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "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.3.0", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", + "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + }, + "engines": { + "node": ">=8" + } + }, + "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/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp/node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/opencollective-postinstall": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz", + "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==", + "dev": true, + "bin": { + "opencollective-postinstall": "index.js" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.0.tgz", + "integrity": "sha512-uhnEDzAbrcJ8R3g2fANnSuXZMBtkpSjxTTgn2LeSiQlfmq72enQJWdQllXW24MBLYnA1SBD2vfvx2o0Zw3Ielw==", + "dev": true, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "dependencies": { + "semver-compare": "^1.0.0" + } + }, + "node_modules/prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "dependencies": { + "path-parse": "^1.0.6" + } + }, + "node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "dependencies": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor/node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", + "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/run-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz", + "integrity": "sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==", + "dev": true, + "bin": { + "run-node": "run-node" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", + "dev": true + }, + "node_modules/rxjs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + }, + "node_modules/tslint": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.0.tgz", + "integrity": "sha512-2vqIvkMHbnx8acMogAERQ/IuINOq6DFqgF8/VDvhEkBqQh/x6SP0Y+OHnKth9/ZcHQSroOZwUQSN18v8KKF0/g==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev" + } + }, + "node_modules/tslint-config-prettier": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz", + "integrity": "sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==", + "dev": true, + "bin": { + "tslint-config-prettier-check": "bin/check.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + } + }, + "node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/typescript": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz", + "integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/wrap-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", + "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", + "dev": true, + "dependencies": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + }, "dependencies": { "@babel/code-frame": { "version": "7.5.5", @@ -1542,9 +3611,9 @@ "dev": true }, "typescript": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", - "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz", + "integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==", "dev": true }, "validate-npm-package-license": { diff --git a/package.json b/package.json index d5fc559d..0c1f6d68 100644 --- a/package.json +++ b/package.json @@ -8,14 +8,13 @@ "lint": "tslint \"./{backend,dashboard}/{,!(node_modules)/**/}/*.ts\"", "codestyle-check": "prettier --check \"./{backend,dashboard}/{,!(node_modules)/**/}/*.ts\"" }, - "dependencies": {}, "devDependencies": { "husky": "^3.0.9", "lint-staged": "^9.4.2", "prettier": "^1.19.1", "tslint": "^5.13.1", "tslint-config-prettier": "^1.18.0", - "typescript": "^4.1.3" + "typescript": "^4.3.4" }, "husky": { "hooks": { diff --git a/presetup-configurator/src/LogChannels.tsx b/presetup-configurator/src/LogChannels.tsx index 4901750b..85300d37 100644 --- a/presetup-configurator/src/LogChannels.tsx +++ b/presetup-configurator/src/LogChannels.tsx @@ -20,8 +20,13 @@ const LOG_TYPES = { "MEMBER_RESTORE": "Member roles restored", "CHANNEL_CREATE": "Channel created", "CHANNEL_DELETE": "Channel deleted", + "CHANNEL_UPDATE": "Channel updated", + "THREAD_CREATE": "Thread created", + "THREAD_DELETE": "Thread deleted", + "THREAD_UPDATE": "Thread updated", "ROLE_CREATE": "Role created", "ROLE_DELETE": "Role deleted", + "ROLE_UPDATE": "Role updated", "MESSAGE_EDIT": "Message edited", "MESSAGE_DELETE": "Message deleted", "MESSAGE_DELETE_BULK": "Messages deleted in bulk", @@ -29,6 +34,15 @@ const LOG_TYPES = { "VOICE_CHANNEL_JOIN": "Voice channel join", "VOICE_CHANNEL_LEAVE": "Voice channel leave", "VOICE_CHANNEL_MOVE": "Voice channel move", + "STAGE_INSTANCE_CREATE": "Stage created", + "STAGE_INSTANCE_DELETE": "Stage deleted", + "STAGE_INSTANCE_UPDATE": "Stage updated", + "EMOJI_CREATE": "Emoji created", + "EMOJI_DELETE": "Emoji deleted", + "EMOJI_UPDATE": "Emoji updated", + "STICKER_CREATE": "Sticker created", + "STICKER_DELETE": "Sticker deleted", + "STICKER_UPDATE": "Sticker updated", "COMMAND": "Command used", "MESSAGE_SPAM_DETECTED": "Message spam detected", "CENSOR": "Message censored",