From b81f9e8b73cb520ad5ae916c3e571ea55f4ca489 Mon Sep 17 00:00:00 2001 From: IRONM00N <64110067+IRONM00N@users.noreply.github.com> Date: Sat, 20 Aug 2022 23:07:02 -0400 Subject: fix ts composite shit, replace got with native fetch, update deps --- .eslintrc.cjs | 14 +- .github/workflows/checks.yml | 2 +- .vscode/settings.json | 12 +- .yarnrc.yml | 6 +- config/index.ts | 2 + config/tsconfig.json | 5 +- ecosystem.config.cjs | 3 +- lib/automod/AutomodShared.ts | 4 +- lib/common/Sentry.ts | 2 +- lib/common/tags.ts | 2 +- lib/extensions/discord-akairo/BushClient.ts | 43 +- lib/extensions/global.ts | 13 - lib/global.ts | 13 + lib/index.ts | 5 + lib/models/index.ts | 13 + lib/models/instance/ActivePunishment.ts | 3 +- lib/models/instance/Guild.ts | 6 +- lib/models/instance/Highlight.ts | 3 +- lib/models/instance/Level.ts | 3 +- lib/models/instance/ModLog.ts | 3 +- lib/models/instance/Reminder.ts | 3 +- lib/models/instance/StickyRole.ts | 3 +- lib/models/shared/Global.ts | 3 +- lib/models/shared/Shared.ts | 3 +- lib/models/shared/Stat.ts | 3 +- lib/tsconfig.json | 6 +- lib/utils/BushClientUtils.ts | 45 +- lib/utils/BushConstants.ts | 2 +- lib/utils/BushUtils.ts | 6 +- lib/utils/ErrorHandler.ts | 236 +++ lib/utils/FormatResponse.ts | 32 + lib/utils/UpdateCache.ts | 36 + neu-item-repo | 2 +- neu-item-repo-dangerous | 2 +- package.json | 58 +- src/bot.ts | 5 +- src/commands/admin/channelPermissions.ts | 2 +- src/commands/admin/roleAll.ts | 1 - src/commands/config/config.ts | 1 - src/commands/config/disable.ts | 2 +- src/commands/dev/eval.ts | 3 +- src/commands/dev/reload.ts | 15 +- src/commands/dev/syncAutomod.ts | 11 +- src/commands/index.ts | 2 +- src/commands/info/help.ts | 2 +- src/commands/leveling/level.ts | 32 +- src/commands/moderation/unmute.ts | 38 +- src/commands/moulberry-bush/capePermissions.ts | 3 +- src/commands/moulberry-bush/capes.ts | 10 +- src/commands/moulberry-bush/serverStatus.ts | 8 +- src/commands/utilities/hash.ts | 82 +- src/commands/utilities/price.ts | 9 +- src/context-menu-commands/user/modlog.ts | 2 +- src/context-menu-commands/user/userInfo.ts | 2 +- src/listeners/commands/commandError.ts | 241 +-- src/listeners/commands/slashCommandError.ts | 5 +- .../contextCommands/contextCommandError.ts | 7 +- src/listeners/member-custom/bushBan.ts | 2 +- src/listeners/message/blacklistedFile.ts | 300 ++-- src/listeners/other/promiseRejection.ts | 5 +- src/listeners/other/uncaughtException.ts | 5 +- src/listeners/other/warning.ts | 5 +- src/tasks/cache/updateCache.ts | 49 +- src/tasks/cache/updatePriceItemCache.ts | 6 +- src/tasks/feature/handleReminders.ts | 5 +- src/tasks/feature/removeExpiredPunishements.ts | 2 +- src/tasks/feature/updateStats.d.ts | 10 + src/tasks/feature/updateStats.js | 22 + src/tasks/feature/updateStats.js.map | 1 + src/tasks/feature/updateStats.ts | 7 - src/tsconfig.json | 6 +- tsconfig.base.json | 15 +- tsconfig.eslint.json | 2 +- tsconfig.json | 9 +- tsconfig.pkg.json | 8 + yarn.lock | 1663 +++----------------- 76 files changed, 979 insertions(+), 2223 deletions(-) create mode 100644 config/index.ts delete mode 100644 lib/extensions/global.ts create mode 100644 lib/global.ts create mode 100644 lib/models/index.ts create mode 100644 lib/utils/ErrorHandler.ts create mode 100644 lib/utils/FormatResponse.ts create mode 100644 lib/utils/UpdateCache.ts create mode 100644 src/tasks/feature/updateStats.d.ts create mode 100644 src/tasks/feature/updateStats.js create mode 100644 src/tasks/feature/updateStats.js.map create mode 100644 tsconfig.pkg.json diff --git a/.eslintrc.cjs b/.eslintrc.cjs index d246897..e65a7c8 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,5 +1,3 @@ -/* eslint-disable import/no-commonjs */ - // prettier-ignore const globals = [ 'NodeFilter', 'AbortController', 'AbortSignal', 'AbstractRange', 'AnalyserNode', 'Animation', 'AnimationEffect', @@ -20,7 +18,7 @@ const globals = [ 'DocumentFragment', 'DocumentTimeline', 'DocumentType', 'DragEvent', 'DynamicsCompressorNode', 'Element', 'ElementInternals', 'ErrorEvent', 'Event', 'EventSource', 'EventTarget', 'External', 'File', 'FileList', 'FileReader', 'FileSystem', 'FileSystemDirectoryEntry', 'FileSystemDirectoryReader', 'FileSystemEntry', 'FileSystemFileEntry', 'FocusEvent', 'FontFace', - 'FontFaceSet', 'FontFaceSetLoadEvent', 'FormData', 'FormDataEvent', 'GainNode', 'Gamepad', 'GamepadButton', 'GamepadEvent', + 'FontFaceSet', 'FontFaceSetLoadEvent', 'FormDataEvent', 'GainNode', 'Gamepad', 'GamepadButton', 'GamepadEvent', 'GamepadHapticActuator', 'Geolocation', 'GeolocationCoordinates', 'GeolocationPosition', 'GeolocationPositionError', 'HTMLAllCollection', 'HTMLAnchorElement', 'HTMLAreaElement', 'HTMLAudioElement', 'HTMLBRElement', 'HTMLBaseElement', 'HTMLBodyElement', 'HTMLButtonElement', 'HTMLCanvasElement', 'HTMLCollection', 'HTMLDListElement', 'HTMLDataElement', @@ -112,7 +110,7 @@ const globals = [ 'ongamepadconnected', 'ongamepaddisconnected', 'onhashchange', 'onlanguagechange', 'onmessage', 'onmessageerror', 'onoffline', 'ononline', 'onpagehide', 'onpageshow', 'onpopstate', 'onrejectionhandled', 'onstorage', 'onunhandledrejection', 'onunload', 'localStorage', 'caches', 'crossOriginIsolated', 'crypto', 'indexedDB', 'isSecureContext', 'origin', 'performance', 'atob', - 'btoa', 'createImageBitmap', 'fetch', 'queueMicrotask', 'sessionStorage', 'addEventListener', 'removeEventListener' + 'btoa', 'createImageBitmap', 'queueMicrotask', 'sessionStorage', 'addEventListener', 'removeEventListener' ] /** @@ -130,8 +128,7 @@ module.exports = { sourceType: 'module', project: './tsconfig.eslint.json' }, - plugins: ['@typescript-eslint', 'deprecation', 'import'], - ignorePatterns: ['dist', 'node_modules'], + plugins: ['@typescript-eslint', 'deprecation'], rules: { 'no-return-await': 'off', '@typescript-eslint/no-empty-interface': 'warn', @@ -170,14 +167,13 @@ module.exports = { 'deprecation/deprecation': 'warn', '@typescript-eslint/explicit-member-accessibility': ['warn', { accessibility: 'explicit' }], '@typescript-eslint/switch-exhaustiveness-check': 'warn', - 'import/no-commonjs': 'error', - 'import/extensions': ['error', 'ignorePackages'], '@typescript-eslint/no-restricted-imports': [ 'error', { paths: [{ name: 'console', importNames: ['assert'], message: 'Import from the `assert` module instead.' }] } ], 'no-restricted-globals': ['error', ...globals.map((v) => ({ name: v, message: "Don't use DOM globals." }))], '@typescript-eslint/no-namespace': 'off', - 'no-debugger': 'warn' + 'no-debugger': 'warn', + '@typescript-eslint/prefer-as-const': 'warn' } }; diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index de5e141..8af3039 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -24,7 +24,7 @@ jobs: - name: ESLint run: yarn lint - name: Build - run: yarn build:tsc + run: yarn build - name: Dry Run run: yarn start:dry diff --git a/.vscode/settings.json b/.vscode/settings.json index d86374b..4eed974 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,8 +9,6 @@ ".pnp.js": false, "**/node_modules": true }, - "javascript.preferences.importModuleSpecifier": "project-relative", - "typescript.preferences.importModuleSpecifier": "project-relative", "search.exclude": { "**/.yarn": true, "**/.pnp.*": true, @@ -20,7 +18,6 @@ }, "editor.codeActionsOnSave": { "source.organizeImports": true, - // "source.fixAll.eslint": true, "source.format": true }, "diffEditor.wordWrap": "on", @@ -31,11 +28,14 @@ "prettier.prettierPath": "node_modules/prettier", "prettier.withNodeModules": true, "prettier.useEditorConfig": false, - // "typescript.tsdk": ".yarn/sdks/typescript/lib", - "typescript.tsdk": "node_modules/typescript/lib", - "typescript.enablePromptUseWorkspaceTsdk": true, + "javascript.format.enable": false, + "javascript.preferences.importModuleSpecifier": "relative", "javascript.preferences.importModuleSpecifierEnding": "js", + "javascript.preferences.useAliasesForRenames": false, + "typescript.format.enable": false, + "typescript.preferences.importModuleSpecifier": "relative", "typescript.preferences.importModuleSpecifierEnding": "js", + "typescript.preferences.useAliasesForRenames": false, "discord.removeDetails": false, "discord.removeLowerDetails": false, "discord.removeRemoteRepository": false, diff --git a/.yarnrc.yml b/.yarnrc.yml index caaf948..39b22a0 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -5,10 +5,10 @@ enableTelemetry: false nodeLinker: pnpm plugins: - - path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs - spec: '@yarnpkg/plugin-typescript' - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs - spec: '@yarnpkg/plugin-interactive-tools' + spec: "@yarnpkg/plugin-interactive-tools" + - path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs + spec: "@yarnpkg/plugin-typescript" pnpEnableEsmLoader: true diff --git a/config/index.ts b/config/index.ts new file mode 100644 index 0000000..7514faa --- /dev/null +++ b/config/index.ts @@ -0,0 +1,2 @@ +export * from './Config.js'; +export { default as config, default } from './options.js'; diff --git a/config/tsconfig.json b/config/tsconfig.json index 46b2d15..583c81a 100644 --- a/config/tsconfig.json +++ b/config/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "../tsconfig.base.json", "compilerOptions": { - "outDir": "../../dist/config" - }, - "files": ["./Config.ts", "example-options.ts", "options.ts"] + "outDir": "../dist/config" + } } diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index b064f76..3bd532f 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -1,4 +1,3 @@ -/* eslint-disable import/no-commonjs */ module.exports = { apps: [ ...['', '-beta'].map((e) => ({ @@ -25,7 +24,7 @@ module.exports = { 'ref': `origin/${e === 'production' ? 'master' : 'beta'}`, 'repo': 'https://github.com/NotEnoughUpdates/bush-bot.git', 'path': `/code/bush-bot${e === 'beta' ? '-beta' : ''}`, - 'post-deploy': `yarn install && yarn build:tsc && pm2 start ecosystem.config.cjs --only bush-bot${ + 'post-deploy': `yarn install && yarn build && pm2 start ecosystem.config.cjs --only bush-bot${ e === 'beta' ? '-beta' : '' }` } diff --git a/lib/automod/AutomodShared.ts b/lib/automod/AutomodShared.ts index 5d031d0..08cde25 100644 --- a/lib/automod/AutomodShared.ts +++ b/lib/automod/AutomodShared.ts @@ -8,11 +8,11 @@ import { PermissionFlagsBits, Snowflake } from 'discord.js'; -import UnmuteCommand from '../../src/commands/moderation/unmute.js'; import * as Moderation from '../common/Moderation.js'; import { unmuteResponse } from '../extensions/discord.js/ExtendedGuildMember.js'; import { colors, emojis } from '../utils/BushConstants.js'; import * as Format from '../utils/Format.js'; +import { formatUnmuteResponse } from '../utils/FormatResponse.js'; /** * Handles shared auto moderation functionality. @@ -207,7 +207,7 @@ export async function handleAutomodInteraction(interaction: ButtonInteraction) { if (check !== true) return interaction.reply({ content: check, ephemeral: true }); const check2 = await Moderation.checkMutePermissions(interaction.guild); - if (check2 !== true) return interaction.reply({ content: UnmuteCommand.formatCode('/', victim!, check2), ephemeral: true }); + if (check2 !== true) return interaction.reply({ content: formatUnmuteResponse('/', victim!, check2), ephemeral: true }); const result = await victim.bushUnmute({ reason, diff --git a/lib/common/Sentry.ts b/lib/common/Sentry.ts index 446ec27..1b0e19a 100644 --- a/lib/common/Sentry.ts +++ b/lib/common/Sentry.ts @@ -1,7 +1,7 @@ +import type { Config } from '#config'; import { RewriteFrames } from '@sentry/integrations'; import * as SentryNode from '@sentry/node'; import { Integrations } from '@sentry/node'; -import type { Config } from '../../config/Config.js'; export class Sentry { public constructor(rootdir: string, config: Config) { diff --git a/lib/common/tags.ts b/lib/common/tags.ts index 098cf29..4af8783 100644 --- a/lib/common/tags.ts +++ b/lib/common/tags.ts @@ -1,5 +1,5 @@ /* these functions are adapted from the common-tags npm package which is licensed under the MIT license */ -/* the js docs are adapted from the @types/common-tags npm package which is licensed under the MIT license */ +/* the JSDOCs are adapted from the @types/common-tags npm package which is licensed under the MIT license */ /** * Strips the **initial** indentation from the beginning of each line in a multiline string. diff --git a/lib/extensions/discord-akairo/BushClient.ts b/lib/extensions/discord-akairo/BushClient.ts index 1a6bb8c..92968d6 100644 --- a/lib/extensions/discord-akairo/BushClient.ts +++ b/lib/extensions/discord-akairo/BushClient.ts @@ -10,7 +10,8 @@ import { roleWithDuration, snowflake } from '#args'; -import { BushClientEvents, emojis, formatError, inspect } from '#lib'; +import type { Config } from '#config'; +import { BushClientEvents, emojis, formatError, inspect, updateEveryCache } from '#lib'; import { patch, type PatchedElements } from '@notenoughupdates/events-intercept'; import * as Sentry from '@sentry/node'; import { @@ -44,26 +45,25 @@ import type EventEmitter from 'events'; import { google } from 'googleapis'; import path from 'path'; import readline from 'readline'; -import type { Options as SequelizeOptions, Sequelize as SequelizeType } from 'sequelize'; +import { Options as SequelizeOptions, Sequelize, Sequelize as SequelizeType } from 'sequelize'; import { fileURLToPath } from 'url'; -import type { Config } from '../../../config/Config.js'; -import UpdateCacheTask from '../../../src/tasks/cache/updateCache.js'; -import UpdateStatsTask from '../../../src/tasks/feature/updateStats.js'; import { tinyColor } from '../../arguments/tinyColor.js'; import { BushCache } from '../../common/BushCache.js'; import { HighlightManager } from '../../common/HighlightManager.js'; -import { ActivePunishment } from '../../models/instance/ActivePunishment.js'; -import { Guild as GuildDB } from '../../models/instance/Guild.js'; -import { Highlight } from '../../models/instance/Highlight.js'; -import { Level } from '../../models/instance/Level.js'; -import { ModLog } from '../../models/instance/ModLog.js'; -import { Reminder } from '../../models/instance/Reminder.js'; -import { StickyRole } from '../../models/instance/StickyRole.js'; -import { Global } from '../../models/shared/Global.js'; -import { GuildCount } from '../../models/shared/GuildCount.js'; -import { MemberCount } from '../../models/shared/MemberCount.js'; -import { Shared } from '../../models/shared/Shared.js'; -import { Stat } from '../../models/shared/Stat.js'; +import { + ActivePunishment, + Global, + Guild as GuildModel, + GuildCount, + Highlight, + Level, + MemberCount, + ModLog, + Reminder, + Shared, + Stat, + StickyRole +} from '../../models/index.js'; import { AllowedMentions } from '../../utils/AllowedMentions.js'; import { BushClientUtils } from '../../utils/BushClientUtils.js'; import { BushLogger } from '../../utils/BushLogger.js'; @@ -75,7 +75,6 @@ import { BushCommandHandler } from './BushCommandHandler.js'; import { BushInhibitorHandler } from './BushInhibitorHandler.js'; import { BushListenerHandler } from './BushListenerHandler.js'; import { BushTaskHandler } from './BushTaskHandler.js'; -const { Sequelize } = (await import('sequelize')).default; declare module 'discord.js' { export interface Client extends EventEmitter { @@ -467,7 +466,7 @@ export class BushClient extends AkairoClient extends AkairoClient>.`, false); - const stats = await UpdateStatsTask.init(this); + + const stats = + (await Stat.findByPk(this.config.environment)) ?? (await Stat.create({ environment: this.config.environment })); this.stats.commandsUsed = stats.commandsUsed; this.stats.slashCommandsUsed = stats.slashCommandsUsed; await this.login(this.token!); diff --git a/lib/extensions/global.ts b/lib/extensions/global.ts deleted file mode 100644 index a9020d7..0000000 --- a/lib/extensions/global.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* eslint-disable no-var */ -declare global { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - interface ReadonlyArray { - includes}`>( - this: ReadonlyArray, - searchElement: S, - fromIndex?: number - ): searchElement is R & S; - } -} - -export {}; diff --git a/lib/global.ts b/lib/global.ts new file mode 100644 index 0000000..0a0bcca --- /dev/null +++ b/lib/global.ts @@ -0,0 +1,13 @@ +/* eslint-disable */ + +declare global { + interface ReadonlyArray { + includes}`>( + this: ReadonlyArray, + searchElement: S, + fromIndex?: number + ): searchElement is R & S; + } +} + +export {}; diff --git a/lib/index.ts b/lib/index.ts index 5a8ecde..ca23177 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,3 +1,5 @@ +import './global.js'; + export * from './automod/AutomodShared.js'; export * from './automod/MemberAutomod.js'; export * from './automod/MessageAutomod.js'; @@ -53,4 +55,7 @@ export * as Arg from './utils/Arg.js'; export * from './utils/BushConstants.js'; export * from './utils/BushLogger.js'; export * from './utils/BushUtils.js'; +export * from './utils/ErrorHandler.js'; export * as Format from './utils/Format.js'; +export * from './utils/FormatResponse.js'; +export * from './utils/UpdateCache.js'; diff --git a/lib/models/index.ts b/lib/models/index.ts new file mode 100644 index 0000000..ae82fb7 --- /dev/null +++ b/lib/models/index.ts @@ -0,0 +1,13 @@ +export * from './BaseModel.js'; +export * from './instance/ActivePunishment.js'; +export * from './instance/Guild.js'; +export * from './instance/Highlight.js'; +export * from './instance/Level.js'; +export * from './instance/ModLog.js'; +export * from './instance/Reminder.js'; +export * from './instance/StickyRole.js'; +export * from './shared/Global.js'; +export * from './shared/GuildCount.js'; +export * from './shared/MemberCount.js'; +export * from './shared/Shared.js'; +export * from './shared/Stat.js'; diff --git a/lib/models/instance/ActivePunishment.ts b/lib/models/instance/ActivePunishment.ts index 38012ca..9bd9d01 100644 --- a/lib/models/instance/ActivePunishment.ts +++ b/lib/models/instance/ActivePunishment.ts @@ -1,8 +1,7 @@ import { type Snowflake } from 'discord.js'; import { nanoid } from 'nanoid'; -import { type Sequelize } from 'sequelize'; +import { DataTypes, type Sequelize } from 'sequelize'; import { BaseModel } from '../BaseModel.js'; -const { DataTypes } = (await import('sequelize')).default; export enum ActivePunishmentType { BAN = 'BAN', diff --git a/lib/models/instance/Guild.ts b/lib/models/instance/Guild.ts index f258d48..1d645e9 100644 --- a/lib/models/instance/Guild.ts +++ b/lib/models/instance/Guild.ts @@ -1,9 +1,9 @@ +import config from '#config'; import { ChannelType, Constants, type Snowflake } from 'discord.js'; -import { type Sequelize } from 'sequelize'; +import { DataTypes, type Sequelize } from 'sequelize'; import { BadWordDetails } from '../../automod/AutomodShared.js'; import { type BushClient } from '../../extensions/discord-akairo/BushClient.js'; import { BaseModel } from '../BaseModel.js'; -const { DataTypes } = (await import('sequelize')).default; export interface GuildModel { id: Snowflake; @@ -199,8 +199,6 @@ const asGuildSetting = (et: { [K in keyof T]: PartialBy { const _user = await this.resolveNonCachedUser(user); if (!_user) throw new Error(`Cannot find user ${user}`); - const apiRes = (await got - .get(`https://pronoundb.org/api/v1/lookup?platform=discord&id=${_user.id}`) - .json() + const apiRes = (await fetch(`https://pronoundb.org/api/v1/lookup?platform=discord&id=${_user.id}`) + .then((p) => (p.ok ? p.json() : undefined)) .catch(() => undefined)) as { pronouns: PronounCode } | undefined; if (!apiRes) return undefined; @@ -386,22 +384,23 @@ export class BushClientUtils { public async uploadImageToImgur(image: string) { const clientId = this.client.config.credentials.imgurClientId; - const resp = (await got - .post('https://api.imgur.com/3/upload', { - headers: { - Authorization: `Client-ID ${clientId}`, - Accept: 'application/json' - }, - form: { - image: image, - type: 'base64' - }, - followRedirect: true - }) - .json() - .catch(() => null)) as { data: { link: string } | undefined }; - - return resp.data?.link ?? null; + const formData = new FormData(); + formData.append('type', 'base64'); + formData.append('image', image); + + const resp = (await fetch('https://api.imgur.com/3/upload', { + method: 'POST', + headers: { + Accept: 'application/json', + Authorization: `Client-ID ${clientId}` + }, + body: formData, + redirect: 'follow' + }) + .then((p) => (p.ok ? p.json() : null)) + .catch(() => null)) as { data: { link: string } } | null; + + return resp?.data?.link ?? null; } /** diff --git a/lib/utils/BushConstants.ts b/lib/utils/BushConstants.ts index d3089ec..c65b5e8 100644 --- a/lib/utils/BushConstants.ts +++ b/lib/utils/BushConstants.ts @@ -1,4 +1,4 @@ -import deepLock from 'deep-lock'; +import { default as deepLock } from 'deep-lock'; import { ArgumentMatches as AkairoArgumentMatches, ArgumentTypes as AkairoArgumentTypes, diff --git a/lib/utils/BushUtils.ts b/lib/utils/BushUtils.ts index 34ea461..1922204 100644 --- a/lib/utils/BushUtils.ts +++ b/lib/utils/BushUtils.ts @@ -27,7 +27,6 @@ import { type InteractionReplyOptions, type PermissionsString } from 'discord.js'; -import got from 'got'; import { DeepWritable } from 'ts-essentials'; import { inspect as inspectUtil, promisify } from 'util'; import * as Format from './Format.js'; @@ -86,8 +85,11 @@ export function chunk(arr: T[], perChunk: number): T[][] { * @returns The the uuid of the user. */ export async function mcUUID(username: string, dashed = false): Promise { - const apiRes = (await got.get(`https://api.ashcon.app/mojang/v2/user/${username}`).json()) as UuidRes; + const apiRes = (await fetch(`https://api.ashcon.app/mojang/v2/user/${username}`).then((p) => + p.ok ? p.json() : undefined + )) as UuidRes; + // this will throw an error if response is not ok return dashed ? apiRes.uuid : apiRes.uuid.replace(/-/g, ''); } diff --git a/lib/utils/ErrorHandler.ts b/lib/utils/ErrorHandler.ts new file mode 100644 index 0000000..923da75 --- /dev/null +++ b/lib/utils/ErrorHandler.ts @@ -0,0 +1,236 @@ +import { AkairoMessage, Command } from 'discord-akairo'; +import { ChannelType, Client, EmbedBuilder, escapeInlineCode, GuildTextBasedChannel, Message } from 'discord.js'; +import { BushCommandHandlerEvents } from '../extensions/discord-akairo/BushCommandHandler.js'; +import { SlashMessage } from '../extensions/discord-akairo/SlashMessage.js'; +import { colors } from './BushConstants.js'; +import { capitalize, formatError } from './BushUtils.js'; +import { bold, input } from './Format.js'; + +export async function handleCommandError( + client: Client, + ...[error, message, _command]: BushCommandHandlerEvents['error'] | BushCommandHandlerEvents['slashError'] +) { + try { + const isSlash = message.util?.isSlash; + const errorNum = Math.floor(Math.random() * 6969696969) + 69; // hehe funny number + const channel = + message.channel?.type === ChannelType.DM ? message.channel.recipient?.tag : (message.channel)?.name; + const command = _command ?? message.util?.parsed?.command; + + client.sentry.captureException(error, { + level: 'error', + user: { id: message.author.id, username: message.author.tag }, + extra: { + 'command.name': command?.id, + 'message.id': message.id, + 'message.type': message.util ? (message.util.isSlash ? 'slash' : 'normal') : 'unknown', + 'message.parsed.content': message.util?.parsed?.content, + 'channel.id': + (message.channel?.type === ChannelType.DM ? message.channel.recipient?.id : message.channel?.id) ?? '¯\\_(ツ)_/¯', + 'channel.name': channel, + 'guild.id': message.guild?.id ?? '¯\\_(ツ)_/¯', + 'guild.name': message.guild?.name ?? '¯\\_(ツ)_/¯', + 'environment': client.config.environment + } + }); + + void client.console.error( + `${isSlash ? 'slashC' : 'c'}ommandError`, + `an error occurred with the <<${command}>> ${isSlash ? 'slash ' : ''}command in <<${channel}>> triggered by <<${ + message?.author?.tag + }>>:\n${formatError(error, true)})}`, + false + ); + + const _haste = getErrorHaste(client, error); + const _stack = getErrorStack(client, error); + const [haste, stack] = await Promise.all([_haste, _stack]); + const options = { message, error, isSlash, errorNum, command, channel, haste, stack }; + + const errorEmbed = _generateErrorEmbed({ + ...options, + type: 'command-log' + }); + + void client.logger.channelError({ embeds: errorEmbed }); + + if (message) { + if (!client.config.owners.includes(message.author.id)) { + const errorUserEmbed = _generateErrorEmbed({ + ...options, + type: 'command-user' + }); + void message.util?.send({ embeds: errorUserEmbed }).catch(() => null); + } else { + const errorDevEmbed = _generateErrorEmbed({ + ...options, + type: 'command-dev' + }); + + void message.util?.send({ embeds: errorDevEmbed }).catch(() => null); + } + } + } catch (e) { + throw new IFuckedUpError('An error occurred while handling a command error.', error, e); + } +} + +export async function generateErrorEmbed( + client: Client, + options: + | { + message: Message | AkairoMessage; + error: Error | any; + isSlash?: boolean; + type: 'command-log' | 'command-dev' | 'command-user'; + errorNum: number; + command?: Command; + channel?: string; + } + | { error: Error | any; type: 'uncaughtException' | 'unhandledRejection'; context?: string } +): Promise { + const _haste = getErrorHaste(client, options.error); + const _stack = getErrorStack(client, options.error); + const [haste, stack] = await Promise.all([_haste, _stack]); + + return _generateErrorEmbed({ ...options, haste, stack }); +} + +function _generateErrorEmbed( + options: + | { + message: Message | SlashMessage; + error: Error | any; + isSlash?: boolean; + type: 'command-log' | 'command-dev' | 'command-user'; + errorNum: number; + command?: Command; + channel?: string; + haste: string[]; + stack: string; + } + | { + error: Error | any; + type: 'uncaughtException' | 'unhandledRejection'; + context?: string; + haste: string[]; + stack: string; + } +): EmbedBuilder[] { + const embeds = [new EmbedBuilder().setColor(colors.error)]; + if (options.type === 'command-user') { + embeds[0] + .setTitle('An Error Occurred') + .setDescription( + `Oh no! ${ + options.command ? `While running the ${options.isSlash ? 'slash ' : ''}command ${input(options.command.id)}, a` : 'A' + }n error occurred. Please give the developers code ${input(`${options.errorNum}`)}.` + ) + .setTimestamp(); + return embeds; + } + const description: string[] = []; + + if (options.type === 'command-log') { + description.push( + `**User:** ${options.message.author} (${options.message.author.tag})`, + `**Command:** ${options.command ?? 'N/A'}`, + `**Channel:** <#${options.message.channel?.id}> (${options.channel})`, + `**Message:** [link](${options.message.url})` + ); + if (options.message?.util?.parsed?.content) description.push(`**Command Content:** ${options.message.util.parsed.content}`); + } + + description.push(...options.haste); + + embeds.push(new EmbedBuilder().setColor(colors.error).setTimestamp().setDescription(options.stack.substring(0, 4000))); + if (description.length) embeds[0].setDescription(description.join('\n').substring(0, 4000)); + + if (options.type === 'command-dev' || options.type === 'command-log') + embeds[0].setTitle(`${options.isSlash ? 'Slash ' : ''}CommandError #${input(`${options.errorNum}`)}`); + else if (options.type === 'uncaughtException') + embeds[0].setTitle(`${options.context ? `[${bold(options.context)}] An Error Occurred` : 'Uncaught Exception'}`); + else if (options.type === 'unhandledRejection') + embeds[0].setTitle(`${options.context ? `[${bold(options.context)}] An Error Occurred` : 'Unhandled Promise Rejection'}`); + return embeds; +} + +export async function getErrorHaste(client: Client, error: Error | any): Promise { + const inspectOptions = { + showHidden: false, + depth: 9, + colors: false, + customInspect: true, + showProxy: false, + maxArrayLength: Infinity, + maxStringLength: Infinity, + breakLength: 80, + compact: 3, + sorted: false, + getters: true + }; + + const ret: string[] = []; + const promises: Promise<{ + url?: string | undefined; + error?: 'content too long' | 'substr' | 'unable to post' | undefined; + }>[] = []; + const pair: { + [key: string]: { + url?: string | undefined; + error?: 'content too long' | 'substr' | 'unable to post' | undefined; + }; + } = {}; + + for (const element in error) { + if (['stack', 'name', 'message'].includes(element)) continue; + else if (typeof (error as any)[element] === 'object') { + promises.push(client.utils.inspectCleanRedactHaste((error as any)[element], inspectOptions)); + } + } + + const links = await Promise.all(promises); + + let index = 0; + for (const element in error) { + if (['stack', 'name', 'message'].includes(element)) continue; + else if (typeof (error as any)[element] === 'object') { + pair[element] = links[index]; + index++; + } + } + + for (const element in error) { + if (['stack', 'name', 'message'].includes(element)) continue; + else { + ret.push( + `**Error ${capitalize(element)}:** ${ + typeof error[element] === 'object' + ? `${ + pair[element].url + ? `[haste](${pair[element].url})${pair[element].error ? ` - ${pair[element].error}` : ''}` + : pair[element].error + }` + : `\`${escapeInlineCode(client.utils.inspectAndRedact((error as any)[element], inspectOptions))}\`` + }` + ); + } + } + return ret; +} + +export async function getErrorStack(client: Client, error: Error | any): Promise { + return await client.utils.inspectCleanRedactCodeblock(error, 'js', { colors: false }, 4000); +} + +export class IFuckedUpError extends Error { + public declare original: Error | any; + public declare newError: Error | any; + + public constructor(message: string, original?: Error | any, newError?: Error | any) { + super(message); + this.name = 'IFuckedUpError'; + this.original = original; + this.newError = newError; + } +} diff --git a/lib/utils/FormatResponse.ts b/lib/utils/FormatResponse.ts new file mode 100644 index 0000000..f094601 --- /dev/null +++ b/lib/utils/FormatResponse.ts @@ -0,0 +1,32 @@ +import type { GuildMember } from 'discord.js'; +import { unmuteResponse, UnmuteResponse } from '../extensions/discord.js/ExtendedGuildMember.js'; +import { emojis } from './BushConstants.js'; +import { format } from './BushUtils.js'; +import { input } from './Format.js'; + +export function formatUnmuteResponse(prefix: string, member: GuildMember, code: UnmuteResponse): string { + const error = emojis.error; + const victim = input(member.user.tag); + switch (code) { + case unmuteResponse.MISSING_PERMISSIONS: + return `${error} Could not unmute ${victim} because I am missing the **Manage Roles** permission.`; + case unmuteResponse.NO_MUTE_ROLE: + return `${error} Could not unmute ${victim}, you must set a mute role with \`${prefix}config muteRole\`.`; + case unmuteResponse.MUTE_ROLE_INVALID: + return `${error} Could not unmute ${victim} because the current mute role no longer exists. Please set a new mute role with \`${prefix}config muteRole\`.`; + case unmuteResponse.MUTE_ROLE_NOT_MANAGEABLE: + return `${error} Could not unmute ${victim} because I cannot assign the current mute role, either change the role's position or set a new mute role with \`${prefix}config muteRole\`.`; + case unmuteResponse.ACTION_ERROR: + return `${error} Could not unmute ${victim}, there was an error removing their mute role.`; + case unmuteResponse.MODLOG_ERROR: + return `${error} While muting ${victim}, there was an error creating a modlog entry, please report this to my developers.`; + case unmuteResponse.PUNISHMENT_ENTRY_REMOVE_ERROR: + return `${error} While muting ${victim}, there was an error removing their mute entry, please report this to my developers.`; + case unmuteResponse.DM_ERROR: + return `${emojis.warn} unmuted ${victim} however I could not send them a dm.`; + case unmuteResponse.SUCCESS: + return `${emojis.success} Successfully unmuted ${victim}.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } +} diff --git a/lib/utils/UpdateCache.ts b/lib/utils/UpdateCache.ts new file mode 100644 index 0000000..2f96d9d --- /dev/null +++ b/lib/utils/UpdateCache.ts @@ -0,0 +1,36 @@ +import config from '#config'; +import { Client } from 'discord.js'; +import { Global, Guild, Shared } from '../models/index.js'; + +export async function updateGlobalCache(client: Client) { + const environment = config.environment; + const row: { [x: string]: any } = ((await Global.findByPk(environment)) ?? (await Global.create({ environment }))).toJSON(); + + for (const option in row) { + if (Object.keys(client.cache.global).includes(option)) { + client.cache.global[option as keyof typeof client.cache.global] = row[option]; + } + } +} + +export async function updateSharedCache(client: Client) { + const row: { [x: string]: any } = ((await Shared.findByPk(0)) ?? (await Shared.create())).toJSON(); + + for (const option in row) { + if (Object.keys(client.cache.shared).includes(option)) { + client.cache.shared[option as keyof typeof client.cache.shared] = row[option]; + if (option === 'superUsers') client.superUserID = row[option]; + } + } +} + +export async function updateGuildCache(client: Client) { + const rows = await Guild.findAll(); + for (const row of rows) { + client.cache.guilds.set(row.id, row.toJSON() as Guild); + } +} + +export async function updateEveryCache(client: Client) { + await Promise.all([updateGlobalCache(client), updateSharedCache(client), updateGuildCache(client)]); +} diff --git a/neu-item-repo b/neu-item-repo index 27f73b4..3a694f6 160000 --- a/neu-item-repo +++ b/neu-item-repo @@ -1 +1 @@ -Subproject commit 27f73b42b3aaa20d361e8b29ace2ed3878367820 +Subproject commit 3a694f67fcbacf37b9f2b2d62ac30840601c039d diff --git a/neu-item-repo-dangerous b/neu-item-repo-dangerous index 27f73b4..3a694f6 160000 --- a/neu-item-repo-dangerous +++ b/neu-item-repo-dangerous @@ -1 +1 @@ -Subproject commit 27f73b42b3aaa20d361e8b29ace2ed3878367820 +Subproject commit 3a694f67fcbacf37b9f2b2d62ac30840601c039d diff --git a/package.json b/package.json index 0358079..2c2db00 100644 --- a/package.json +++ b/package.json @@ -21,21 +21,18 @@ ], "license": "CC-BY-NC-SA-4.0", "scripts": { - "build:esbuild": "yarn rimraf dist && yarn esbuild --sourcemap=inline --outdir=dist --platform=node --target=es2020 --format=esm --log-level=warning src/**/*.ts", - "build:tsc": "yarn rimraf dist && yarn tsc", - "build:tsc:no-emit": "yarn tsc --noEmit", - "build:keep": "yarn tsc", - "start:raw": "node --enable-source-maps --experimental-json-modules --no-warnings dist/src/bot.js", - "start:esbuild": "yarn build:esbuild && yarn start:raw", - "start": "yarn build:tsc && yarn start:raw", + "build": "yarn rimraf dist && yarn tsc -b", + "build:no-emit": "yarn tsc --noEmit", + "build:keep": "yarn tsc -b", + "start": "yarn build && yarn start:raw", "start:keep": "yarn build:keep && yarn start:raw", "start:dry": "yarn start dry", - "dev": "yarn build:tsc && yarn start:raw", - "test": "yarn lint && yarn tsc --noEmit", + "start:raw": "node --enable-source-maps --experimental-json-modules --no-warnings dist/src/bot.js", + "test": "yarn lint && yarn build:no-emit", "format": "yarn prettier . --write", - "lint": "yarn eslint src lib config tests", "format:check": "yarn prettier . --check", - "upgrade": "yarn rimraf yarn.lock && yarn cache clean && yarn install && yarn up && yarn up -R && yarn set version latest && git submodule update --recursive --remote", + "lint": "yarn eslint src lib config tests", + "upgrade": "yarn rimraf yarn.lock && yarn cache clean && yarn install && yarn set version latest && git submodule update --recursive --remote && yarn upgrade-interactive", "upgrade:sdk": "yarn dlx @yarnpkg/sdks vscode", "beta": "git push && git checkout beta && git merge master && git push && git checkout master", "deploy:beta": "pm2 deploy ecosystem.config.cjs beta", @@ -46,8 +43,11 @@ "#lib": { "default": "./lib/index.js" }, - "#constants": { - "default": "./lib/utils/BushConstants.js" + "#lib/*": { + "default": "./lib/*" + }, + "#src/*": { + "default": "./src/*" }, "#args": { "default": "./lib/arguments/index.js" @@ -57,6 +57,9 @@ }, "#tags": { "default": "./lib/common/tags.js" + }, + "#config": { + "default": "./config/index.js" } }, "dependencies": { @@ -68,9 +71,9 @@ "@notenoughupdates/humanize-duration": "^4.0.1", "@notenoughupdates/simplify-number": "^1.0.1", "@notenoughupdates/wolfram-alpha-api": "^1.0.2", - "@sentry/integrations": "^7.9.0", - "@sentry/node": "^7.9.0", - "@sentry/tracing": "^7.9.0", + "@sentry/integrations": "^7.11.1", + "@sentry/node": "^7.11.1", + "@sentry/tracing": "^7.11.1", "canvas": "^2.9.3", "chalk": "^5.0.1", "deep-lock": "^1.0.0", @@ -79,8 +82,7 @@ "discord.js": "npm:@notenoughupdates/discord.js@forum", "fuse.js": "^6.6.2", "gif-to-apng": "^0.1.2", - "googleapis": "^105.0.0", - "got": "^12.3.1", + "googleapis": "^107.0.0", "lodash": "^4.17.21", "mathjs": "^11.0.1", "nanoid": "^4.0.0", @@ -90,34 +92,32 @@ "prettier": "^2.7.1", "pretty-bytes": "^6.0.0", "rimraf": "^3.0.2", - "sequelize": "6.21.3", + "sequelize": "6.21.4", "tinycolor2": "^1.4.2", "typescript": "^4.7.4", "vm2": "^3.9.10" }, "devDependencies": { "@sapphire/snowflake": "^3.2.2", - "@sentry/types": "^7.9.0", - "@types/eslint": "^8.4.5", + "@sentry/types": "^7.11.1", + "@types/eslint": "^8.4.6", "@types/express": "^4.17.13", - "@types/lodash": "^4.14.182", - "@types/node": "^18.6.5", + "@types/lodash": "^4.14.184", + "@types/node": "^18.7.8", "@types/numeral": "^2.0.2", "@types/pg": "^8.6.5", "@types/prettier": "^2.7.0", "@types/rimraf": "^3.0.2", "@types/tinycolor2": "^1.4.3", "@types/validator": "^13.7.5", - "@typescript-eslint/eslint-plugin": "^5.33.0", - "@typescript-eslint/parser": "^5.33.0", - "electron": "^20.0.1", - "eslint": "^8.21.0", + "@typescript-eslint/eslint-plugin": "^5.33.1", + "@typescript-eslint/parser": "^5.33.1", + "eslint": "^8.22.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-deprecation": "^1.3.2", - "eslint-plugin-import": "^2.26.0", "node-fetch": "^3.2.10", "ts-essentials": "^9.2.0", - "vitest": "^0.21.1" + "vitest": "^0.22.1" }, "packageManager": "yarn@3.2.2", "resolutions": { diff --git a/src/bot.ts b/src/bot.ts index 10818e9..ed8ca65 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1,11 +1,13 @@ +console.log('Tanzanite is Starting'); + import { init } from '../lib/utils/BushLogger.js'; // creates proxies on console.log and console.warn // also starts a REPL session init(); +import { config } from '#config'; import { dirname } from 'path'; import { fileURLToPath } from 'url'; -import { default as config } from '../config/options.js'; import { Sentry } from '../lib/common/Sentry.js'; import { BushClient } from '../lib/extensions/discord-akairo/BushClient.js'; @@ -16,7 +18,6 @@ const client = new BushClient(config); if (!isDry) await client.dbPreInit(); await client.init(); if (isDry) { - await client.destroy(); process.exit(0); } else { await client.start(); diff --git a/src/commands/admin/channelPermissions.ts b/src/commands/admin/channelPermissions.ts index 21abd04..0b09e54 100644 --- a/src/commands/admin/channelPermissions.ts +++ b/src/commands/admin/channelPermissions.ts @@ -1,5 +1,6 @@ import { Arg, + BushCommand, ButtonPaginator, clientSendAndPermCheck, emojis, @@ -10,7 +11,6 @@ import { } from '#lib'; import assert from 'assert/strict'; import { ApplicationCommandOptionType, EmbedBuilder, PermissionFlagsBits } from 'discord.js'; -import { BushCommand } from '../../../lib/extensions/discord-akairo/BushCommand.js'; export default class ChannelPermissionsCommand extends BushCommand { public constructor() { diff --git a/src/commands/admin/roleAll.ts b/src/commands/admin/roleAll.ts index a48bd6b..54afc2a 100644 --- a/src/commands/admin/roleAll.ts +++ b/src/commands/admin/roleAll.ts @@ -7,7 +7,6 @@ import { type CommandMessage, type SlashMessage } from '#lib'; - import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; diff --git a/src/commands/config/config.ts b/src/commands/config/config.ts index f6b8bfa..39a44d1 100644 --- a/src/commands/config/config.ts +++ b/src/commands/config/config.ts @@ -15,7 +15,6 @@ import { type SlashMessage } from '#lib'; import assert from 'assert/strict'; - import { type ArgumentGeneratorReturn, type SlashOption } from 'discord-akairo'; import { ActionRowBuilder, diff --git a/src/commands/config/disable.ts b/src/commands/config/disable.ts index 00dea76..f9abb4a 100644 --- a/src/commands/config/disable.ts +++ b/src/commands/config/disable.ts @@ -11,7 +11,7 @@ import { } from '#lib'; import assert from 'assert/strict'; import { ApplicationCommandOptionType, AutocompleteInteraction, PermissionFlagsBits } from 'discord.js'; -import Fuse from 'fuse.js'; +import { default as Fuse } from 'fuse.js'; assert(Fuse); diff --git a/src/commands/dev/eval.ts b/src/commands/dev/eval.ts index 04db2eb..fdef3ac 100644 --- a/src/commands/dev/eval.ts +++ b/src/commands/dev/eval.ts @@ -45,7 +45,6 @@ import { ReactionCollector, SelectMenuComponent } from 'discord.js'; -import got from 'got'; import path from 'path'; import ts from 'typescript'; import { fileURLToPath } from 'url'; @@ -57,7 +56,7 @@ const { transpile } = ts, /* eslint-enable @typescript-eslint/no-unused-vars */ // prettier-ignore -assertAll(ActivePunishment, BushCommand, Global, Guild, Level, ModLog, Shared, StickyRole, Snowflake_, Canvas, exec, ActionRow, ButtonComponent, ButtonInteraction, Collection, Collector, CommandInteraction, ContextMenuCommandInteraction, DMChannel, Embed, Emoji, InteractionCollector, Message, Attachment, MessageCollector, OAuth2Scopes, PermissionFlagsBits, PermissionsBitField, ReactionCollector, SelectMenuComponent, path, ts, fileURLToPath, promisify, assert, got, transpile, sh, SnowflakeUtil, __dirname); +assertAll(ActivePunishment, BushCommand, Global, Guild, Level, ModLog, Shared, StickyRole, Snowflake_, Canvas, exec, ActionRow, ButtonComponent, ButtonInteraction, Collection, Collector, CommandInteraction, ContextMenuCommandInteraction, DMChannel, Embed, Emoji, InteractionCollector, Message, Attachment, MessageCollector, OAuth2Scopes, PermissionFlagsBits, PermissionsBitField, ReactionCollector, SelectMenuComponent, path, ts, fileURLToPath, promisify, assert, transpile, sh, SnowflakeUtil, __dirname); export default class EvalCommand extends BushCommand { public constructor() { diff --git a/src/commands/dev/reload.ts b/src/commands/dev/reload.ts index 40d53eb..8125015 100644 --- a/src/commands/dev/reload.ts +++ b/src/commands/dev/reload.ts @@ -8,17 +8,6 @@ export default class ReloadCommand extends BushCommand { description: 'Reloads the bot', usage: ['reload'], examples: ['reload'], - // args: [ - // { - // id: 'fast', - // description: 'Whether or not to use esbuild for fast compiling.', - // match: 'flag', - // flag: ['--fast'], - // prompt: 'Would you like to use esbuild for fast compiling?', - // optional: true, - // slashType: ApplicationCommandOptionType.Boolean - // } - // ], ownerOnly: true, typing: true, slash: true, @@ -27,13 +16,13 @@ export default class ReloadCommand extends BushCommand { }); } - public override async exec(message: CommandMessage | SlashMessage /* args: { fast: ArgType<'flag'> } */) { + public override async exec(message: CommandMessage | SlashMessage) { if (!message.author.isOwner()) return await message.util.reply(`${emojis.error} Only my developers can run this command.`); let output: { stdout: string; stderr: string }; try { const s = new Date(); - output = await shell(`yarn build:${/* args.fast ? 'esbuild' : */ 'tsc'}`); + output = await shell(`yarn build`); await Promise.all([ this.client.commandHandler.reloadAll(), this.client.listenerHandler.reloadAll(), diff --git a/src/commands/dev/syncAutomod.ts b/src/commands/dev/syncAutomod.ts index c78e6c0..3dbd0be 100644 --- a/src/commands/dev/syncAutomod.ts +++ b/src/commands/dev/syncAutomod.ts @@ -1,5 +1,4 @@ import { BushCommand, clientSendAndPermCheck, emojis, Shared, type CommandMessage, type SlashMessage } from '#lib'; -import got from 'got'; import typescript from 'typescript'; import { NodeVM } from 'vm2'; @@ -22,10 +21,12 @@ export default class SyncAutomodCommand extends BushCommand { if (!message.author.isOwner() && message.author.id !== '497789163555389441') return await message.util.reply(`${emojis.error} Only a very select few may use this command.`); - const badLinks = (await got.get('https://raw.githubusercontent.com/NotEnoughUpdates/bush-bot/master/src/lib/badlinks.ts')) - .body; - const badWords = (await got.get('https://raw.githubusercontent.com/NotEnoughUpdates/bush-bot/master/src/lib/badwords.ts')) - .body; + const badLinks = await fetch('https://raw.githubusercontent.com/NotEnoughUpdates/bush-bot/master/src/lib/badlinks.ts').then( + (p) => p.text() + ); + const badWords = await fetch('https://raw.githubusercontent.com/NotEnoughUpdates/bush-bot/master/src/lib/badwords.ts').then( + (p) => p.text() + ); const transpiledBadLinks = typescript.transpileModule(badLinks, {}).outputText; const transpiledBadWords = typescript.transpileModule(badWords, {}).outputText; diff --git a/src/commands/index.ts b/src/commands/index.ts index a9db0de..59801c7 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -62,7 +62,7 @@ export { default as ServerStatusCommand } from './moulberry-bush/serverStatus.js export { default as ActivityCommand } from './utilities/activity.js'; export { default as CalculatorCommand } from './utilities/calculator.js'; export { default as DecodeCommand } from './utilities/decode.js'; -export { default as HashCommand } from './utilities/hash.js'; +// export { default as HashCommand } from './utilities/hash.js'; export { default as PriceCommand } from './utilities/price.js'; export { default as RemindCommand } from './utilities/remind.js'; export { default as RemindersCommand } from './utilities/reminders.js'; diff --git a/src/commands/info/help.ts b/src/commands/info/help.ts index 62f177e..df18403 100644 --- a/src/commands/info/help.ts +++ b/src/commands/info/help.ts @@ -20,7 +20,7 @@ import { EmbedBuilder, PermissionFlagsBits } from 'discord.js'; -import Fuse from 'fuse.js'; +import { default as Fuse } from 'fuse.js'; import packageDotJSON from '../../../package.json' assert { type: 'json' }; assert(Fuse); diff --git a/src/commands/leveling/level.ts b/src/commands/leveling/level.ts index eda43f2..219ae19 100644 --- a/src/commands/leveling/level.ts +++ b/src/commands/leveling/level.ts @@ -13,11 +13,7 @@ import { SimplifyNumber } from '@notenoughupdates/simplify-number'; import assert from 'assert/strict'; import canvas from 'canvas'; import { ApplicationCommandOptionType, AttachmentBuilder, Guild, PermissionFlagsBits, User } from 'discord.js'; -import got from 'got'; -import { dirname, join } from 'path'; -import { fileURLToPath } from 'url'; assert(canvas); -assert(got); assert(SimplifyNumber); export default class LevelCommand extends BushCommand { @@ -85,10 +81,12 @@ export default class LevelCommand extends BushCommand { const white = '#FFFFFF', gray = '#23272A', highlight = user.hexAccentColor ?? '#5865F2'; - // Load roboto font + + // ! Broken on node v18 - install the font instead + /* // Load roboto font canvas.registerFont(join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..', 'assets', 'Roboto-Regular.ttf'), { family: 'Roboto' - }); + }); */ // Create image canvas const levelCard = canvas.createCanvas(800, 200), ctx = levelCard.getContext('2d'); @@ -97,9 +95,14 @@ export default class LevelCommand extends BushCommand { ctx.fillRect(0, 0, levelCard.width, levelCard.height); // Draw avatar const AVATAR_SIZE = 128; - const avatarBuffer = await got.get(user.displayAvatarURL({ extension: 'png', size: AVATAR_SIZE })).buffer(); const avatarImage = new canvas.Image(); - avatarImage.src = avatarBuffer; + avatarImage.src = user.displayAvatarURL({ extension: 'png', size: AVATAR_SIZE }); + + await new Promise((resolve, reject) => { + avatarImage.onload = () => resolve(undefined); + avatarImage.onerror = (e) => reject(e); + }); + const imageTopCoord = levelCard.height / 2 - AVATAR_SIZE / 2; ctx.drawImage(avatarImage, imageTopCoord, imageTopCoord, AVATAR_SIZE, AVATAR_SIZE); // Write tag of user @@ -123,13 +126,12 @@ export default class LevelCommand extends BushCommand { progressBar.draw(); // Draw level data text ctx.fillStyle = white; - ctx.fillText( - `Level: ${userLevel} XP: ${SimplifyNumber(currentLevelXpProgress)}/${SimplifyNumber( - xpForNextLevel - )} Rank: ${SimplifyNumber(rank.indexOf(rank.find((x) => x.user === user.id)!) + 1)}`, - AVATAR_SIZE + 70, - AVATAR_SIZE - 20 - ); + + const xpTxt = `${SimplifyNumber(currentLevelXpProgress)}/${SimplifyNumber(xpForNextLevel)}`; + + const rankTxt = SimplifyNumber(rank.indexOf(rank.find((x) => x.user === user.id)!) + 1); + + ctx.fillText(`Level: ${userLevel} XP: ${xpTxt} Rank: ${rankTxt}`, AVATAR_SIZE + 70, AVATAR_SIZE - 20); // Return image in buffer form return levelCard.toBuffer(); } diff --git a/src/commands/moderation/unmute.ts b/src/commands/moderation/unmute.ts index 620f499..f1e74ab 100644 --- a/src/commands/moderation/unmute.ts +++ b/src/commands/moderation/unmute.ts @@ -1,19 +1,16 @@ import { AllowedMentions, clientSendAndPermCheck, - emojis, - format, + formatUnmuteResponse, Moderation, - unmuteResponse, userGuildPermCheck, type ArgType, type CommandMessage, type OptArgType, - type SlashMessage, - type UnmuteResponse + type SlashMessage } from '#lib'; import assert from 'assert/strict'; -import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; +import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js'; import { BushCommand } from '../../../lib/extensions/discord-akairo/BushCommand.js'; export default class UnmuteCommand extends BushCommand { @@ -83,35 +80,8 @@ export default class UnmuteCommand extends BushCommand { }); return await message.util.reply({ - content: UnmuteCommand.formatCode(member.client.utils.prefix(message), member, responseCode), + content: formatUnmuteResponse(member.client.utils.prefix(message), member, responseCode), allowedMentions: AllowedMentions.none() }); } - - public static formatCode(prefix: string, member: GuildMember, code: UnmuteResponse): string { - const error = emojis.error; - const victim = format.input(member.user.tag); - switch (code) { - case unmuteResponse.MISSING_PERMISSIONS: - return `${error} Could not unmute ${victim} because I am missing the **Manage Roles** permission.`; - case unmuteResponse.NO_MUTE_ROLE: - return `${error} Could not unmute ${victim}, you must set a mute role with \`${prefix}config muteRole\`.`; - case unmuteResponse.MUTE_ROLE_INVALID: - return `${error} Could not unmute ${victim} because the current mute role no longer exists. Please set a new mute role with \`${prefix}config muteRole\`.`; - case unmuteResponse.MUTE_ROLE_NOT_MANAGEABLE: - return `${error} Could not unmute ${victim} because I cannot assign the current mute role, either change the role's position or set a new mute role with \`${prefix}config muteRole\`.`; - case unmuteResponse.ACTION_ERROR: - return `${error} Could not unmute ${victim}, there was an error removing their mute role.`; - case unmuteResponse.MODLOG_ERROR: - return `${error} While muting ${victim}, there was an error creating a modlog entry, please report this to my developers.`; - case unmuteResponse.PUNISHMENT_ENTRY_REMOVE_ERROR: - return `${error} While muting ${victim}, there was an error removing their mute entry, please report this to my developers.`; - case unmuteResponse.DM_ERROR: - return `${emojis.warn} unmuted ${victim} however I could not send them a dm.`; - case unmuteResponse.SUCCESS: - return `${emojis.success} Successfully unmuted ${victim}.`; - default: - return `${emojis.error} An error occurred: ${format.input(code)}}`; - } - } } diff --git a/src/commands/moulberry-bush/capePermissions.ts b/src/commands/moulberry-bush/capePermissions.ts index 3ad9602..793ac59 100644 --- a/src/commands/moulberry-bush/capePermissions.ts +++ b/src/commands/moulberry-bush/capePermissions.ts @@ -11,7 +11,6 @@ import { type SlashMessage } from '#lib'; import { ApplicationCommandOptionType, EmbedBuilder, PermissionFlagsBits } from 'discord.js'; -import got from 'got'; export default class CapePermissionsCommand extends BushCommand { public constructor() { @@ -50,7 +49,7 @@ export default class CapePermissionsCommand extends BushCommand { } try { - capePerms = await got.get('http://moulberry.codes/permscapes.json').json(); + capePerms = await fetch('http://moulberry.codes/permscapes.json').then((p) => (p.ok ? p.json() : null)); } catch (error) { capePerms = null; } diff --git a/src/commands/moulberry-bush/capes.ts b/src/commands/moulberry-bush/capes.ts index 8693dba..6ffc540 100644 --- a/src/commands/moulberry-bush/capes.ts +++ b/src/commands/moulberry-bush/capes.ts @@ -14,11 +14,9 @@ import { } from '#lib'; import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits, type APIEmbed, type AutocompleteInteraction } from 'discord.js'; -import Fuse from 'fuse.js'; -import got from 'got'; +import { default as Fuse } from 'fuse.js'; assert(Fuse); -assert(got); export default class CapesCommand extends BushCommand { public constructor() { @@ -47,9 +45,9 @@ export default class CapesCommand extends BushCommand { } public override async exec(message: CommandMessage | SlashMessage, args: { cape: OptArgType<'string'> }) { - const { tree: neuFileTree }: GithubTreeApi = await got - .get('https://api.github.com/repos/NotEnoughUpdates/NotEnoughUpdates/git/trees/master?recursive=1') - .json();