From 2356d2c44736fb83021dacb551625852111c8ce6 Mon Sep 17 00:00:00 2001 From: IRONM00N <64110067+IRONM00N@users.noreply.github.com> Date: Thu, 18 Aug 2022 22:42:12 -0400 Subject: restructure, experimental presence and member automod, fixed bugs probably made some more bugs --- .eslintrc.cjs | 2 +- .gitignore | 1 + .vscode/settings.json | 5 +- config/tsconfig.json | 7 + lib/arguments/abbreviatedNumber.ts | 13 + lib/arguments/contentWithDuration.ts | 5 + lib/arguments/discordEmoji.ts | 14 + lib/arguments/duration.ts | 5 + lib/arguments/durationSeconds.ts | 6 + lib/arguments/globalUser.ts | 7 + lib/arguments/index.ts | 10 + lib/arguments/messageLink.ts | 20 + lib/arguments/permission.ts | 12 + lib/arguments/roleWithDuration.ts | 17 + lib/arguments/snowflake.ts | 8 + lib/arguments/tinyColor.ts | 10 + lib/automod/AutomodShared.ts | 310 + lib/automod/MemberAutomod.ts | 72 + lib/automod/MessageAutomod.ts | 286 + lib/automod/PresenceAutomod.ts | 85 + lib/badlinks.ts | 6930 ++++++++++++++++++++ lib/badwords.ts | 845 +++ lib/common/BushCache.ts | 26 + lib/common/ButtonPaginator.ts | 224 + lib/common/CanvasProgressBar.ts | 83 + lib/common/ConfirmationPrompt.ts | 64 + lib/common/DeleteButton.ts | 78 + lib/common/HighlightManager.ts | 488 ++ lib/common/Moderation.ts | 556 ++ lib/common/Sentry.ts | 24 + lib/common/tags.ts | 34 + .../discord-akairo/BushArgumentTypeCaster.ts | 3 + lib/extensions/discord-akairo/BushClient.ts | 600 ++ lib/extensions/discord-akairo/BushCommand.ts | 586 ++ .../discord-akairo/BushCommandHandler.ts | 37 + lib/extensions/discord-akairo/BushInhibitor.ts | 19 + .../discord-akairo/BushInhibitorHandler.ts | 3 + lib/extensions/discord-akairo/BushListener.ts | 3 + .../discord-akairo/BushListenerHandler.ts | 3 + lib/extensions/discord-akairo/BushTask.ts | 3 + lib/extensions/discord-akairo/BushTaskHandler.ts | 3 + lib/extensions/discord-akairo/SlashMessage.ts | 3 + lib/extensions/discord.js/BushClientEvents.ts | 200 + lib/extensions/discord.js/ExtendedGuild.ts | 919 +++ lib/extensions/discord.js/ExtendedGuildMember.ts | 1255 ++++ lib/extensions/discord.js/ExtendedMessage.ts | 12 + lib/extensions/discord.js/ExtendedUser.ts | 35 + lib/extensions/global.ts | 13 + lib/index.ts | 56 + lib/models/BaseModel.ts | 13 + lib/models/instance/ActivePunishment.ts | 94 + lib/models/instance/Guild.ts | 431 ++ lib/models/instance/Highlight.ts | 81 + lib/models/instance/Level.ts | 70 + lib/models/instance/ModLog.ts | 127 + lib/models/instance/Reminder.ts | 84 + lib/models/instance/StickyRole.ts | 58 + lib/models/shared/Global.ts | 67 + lib/models/shared/GuildCount.ts | 38 + lib/models/shared/MemberCount.ts | 37 + lib/models/shared/Shared.ts | 84 + lib/models/shared/Stat.ts | 72 + lib/tsconfig.json | 9 + lib/types/BushInspectOptions.ts | 123 + lib/types/CodeBlockLang.ts | 311 + lib/utils/AllowedMentions.ts | 68 + lib/utils/Arg.ts | 192 + lib/utils/BushClientUtils.ts | 499 ++ lib/utils/BushConstants.ts | 531 ++ lib/utils/BushLogger.ts | 315 + lib/utils/BushUtils.ts | 613 ++ lib/utils/Format.ts | 119 + lib/utils/Minecraft.ts | 351 + lib/utils/Minecraft_Test.ts | 86 + misc/test.js | 365 ++ misc/test.png | Bin 0 -> 41015 bytes misc/tooltips.nnb | 118 + package.json | 14 +- src/arguments/abbreviatedNumber.ts | 13 - src/arguments/contentWithDuration.ts | 5 - src/arguments/discordEmoji.ts | 14 - src/arguments/duration.ts | 5 - src/arguments/durationSeconds.ts | 6 - src/arguments/globalUser.ts | 7 - src/arguments/index.ts | 10 - src/arguments/messageLink.ts | 20 - src/arguments/permission.ts | 12 - src/arguments/roleWithDuration.ts | 17 - src/arguments/snowflake.ts | 8 - src/arguments/tinyColor.ts | 10 - src/bot.ts | 6 +- src/commands/admin/channelPermissions.ts | 2 +- src/commands/dev/test.ts | 6 +- src/commands/info/help.ts | 2 +- src/commands/moderation/massEvidence.ts | 2 +- src/commands/moderation/myLogs.ts | 2 +- src/commands/moderation/unmute.ts | 2 +- src/commands/moulberry-bush/neuRepo.ts | 2 +- src/commands/moulberry-bush/rule.ts | 2 +- src/commands/utilities/calculator.ts | 2 +- src/commands/utilities/highlight-block.ts | 2 +- src/commands/utilities/highlight-unblock.ts | 2 +- src/commands/utilities/uuid.ts | 2 +- src/commands/utilities/wolframAlpha.ts | 2 + src/context-menu-commands/message/viewRaw.ts | 2 +- src/context-menu-commands/user/modlog.ts | 2 +- src/context-menu-commands/user/userInfo.ts | 2 +- src/lib/badlinks.ts | 6930 -------------------- src/lib/badwords.ts | 752 --- src/lib/common/AutoMod.ts | 529 -- src/lib/common/ButtonPaginator.ts | 219 - src/lib/common/ConfirmationPrompt.ts | 64 - src/lib/common/DeleteButton.ts | 78 - src/lib/common/HighlightManager.ts | 485 -- src/lib/common/Sentry.ts | 24 - src/lib/common/tags.ts | 34 - src/lib/common/typings/BushInspectOptions.ts | 123 - src/lib/common/typings/CodeBlockLang.ts | 311 - src/lib/common/util/Arg.ts | 192 - src/lib/common/util/Format.ts | 119 - src/lib/common/util/Minecraft.ts | 349 - src/lib/common/util/Minecraft_Test.ts | 86 - src/lib/common/util/Moderation.ts | 556 -- .../discord-akairo/BushArgumentTypeCaster.ts | 3 - src/lib/extensions/discord-akairo/BushClient.ts | 586 -- src/lib/extensions/discord-akairo/BushCommand.ts | 586 -- .../discord-akairo/BushCommandHandler.ts | 37 - src/lib/extensions/discord-akairo/BushInhibitor.ts | 19 - .../discord-akairo/BushInhibitorHandler.ts | 3 - src/lib/extensions/discord-akairo/BushListener.ts | 3 - .../discord-akairo/BushListenerHandler.ts | 3 - src/lib/extensions/discord-akairo/BushTask.ts | 3 - .../extensions/discord-akairo/BushTaskHandler.ts | 3 - src/lib/extensions/discord-akairo/SlashMessage.ts | 3 - src/lib/extensions/discord.js/BushClientEvents.ts | 200 - src/lib/extensions/discord.js/ExtendedGuild.ts | 916 --- .../extensions/discord.js/ExtendedGuildMember.ts | 1255 ---- src/lib/extensions/discord.js/ExtendedMessage.ts | 12 - src/lib/extensions/discord.js/ExtendedUser.ts | 35 - src/lib/extensions/global.ts | 13 - src/lib/index.ts | 53 - src/lib/models/BaseModel.ts | 13 - src/lib/models/instance/ActivePunishment.ts | 94 - src/lib/models/instance/Guild.ts | 422 -- src/lib/models/instance/Highlight.ts | 81 - src/lib/models/instance/Level.ts | 70 - src/lib/models/instance/ModLog.ts | 127 - src/lib/models/instance/Reminder.ts | 84 - src/lib/models/instance/StickyRole.ts | 58 - src/lib/models/shared/Global.ts | 67 - src/lib/models/shared/GuildCount.ts | 39 - src/lib/models/shared/MemberCount.ts | 38 - src/lib/models/shared/Shared.ts | 84 - src/lib/models/shared/Stat.ts | 72 - src/lib/utils/AllowedMentions.ts | 68 - src/lib/utils/BushCache.ts | 26 - src/lib/utils/BushClientUtils.ts | 498 -- src/lib/utils/BushConstants.ts | 531 -- src/lib/utils/BushLogger.ts | 315 - src/lib/utils/BushUtils.ts | 612 -- src/lib/utils/CanvasProgressBar.ts | 83 - src/listeners/automod/automodCreate.ts | 16 + src/listeners/automod/automodUpdate.ts | 17 + src/listeners/automod/memberAutomod.ts | 21 + src/listeners/automod/presenceAutomod.ts | 27 + src/listeners/commands/commandError.ts | 4 +- src/listeners/interaction/interactionCreate.ts | 4 +- src/listeners/member-custom/bushLevelUpdate.ts | 6 +- src/listeners/message/automodCreate.ts | 15 - src/listeners/message/automodUpdate.ts | 17 - src/tasks/cache/updateCache.ts | 4 +- src/tasks/cache/updateHighlightCache.ts | 4 +- src/tasks/cache/updatePriceItemCache.ts | 7 +- src/tasks/feature/handleReminders.ts | 3 +- src/tasks/stats/guildCount.ts | 2 +- src/tsconfig.json | 9 + test.js | 365 -- test.png | Bin 41015 -> 0 bytes tests/arguments/abbreviatedNumber.test.ts | 4 +- tooltips.nnb | 118 - tsconfig.base.json | 67 + tsconfig.eslint.json | 19 +- tsconfig.json | 33 +- 183 files changed, 19172 insertions(+), 18695 deletions(-) create mode 100644 config/tsconfig.json create mode 100644 lib/arguments/abbreviatedNumber.ts create mode 100644 lib/arguments/contentWithDuration.ts create mode 100644 lib/arguments/discordEmoji.ts create mode 100644 lib/arguments/duration.ts create mode 100644 lib/arguments/durationSeconds.ts create mode 100644 lib/arguments/globalUser.ts create mode 100644 lib/arguments/index.ts create mode 100644 lib/arguments/messageLink.ts create mode 100644 lib/arguments/permission.ts create mode 100644 lib/arguments/roleWithDuration.ts create mode 100644 lib/arguments/snowflake.ts create mode 100644 lib/arguments/tinyColor.ts create mode 100644 lib/automod/AutomodShared.ts create mode 100644 lib/automod/MemberAutomod.ts create mode 100644 lib/automod/MessageAutomod.ts create mode 100644 lib/automod/PresenceAutomod.ts create mode 100644 lib/badlinks.ts create mode 100644 lib/badwords.ts create mode 100644 lib/common/BushCache.ts create mode 100644 lib/common/ButtonPaginator.ts create mode 100644 lib/common/CanvasProgressBar.ts create mode 100644 lib/common/ConfirmationPrompt.ts create mode 100644 lib/common/DeleteButton.ts create mode 100644 lib/common/HighlightManager.ts create mode 100644 lib/common/Moderation.ts create mode 100644 lib/common/Sentry.ts create mode 100644 lib/common/tags.ts create mode 100644 lib/extensions/discord-akairo/BushArgumentTypeCaster.ts create mode 100644 lib/extensions/discord-akairo/BushClient.ts create mode 100644 lib/extensions/discord-akairo/BushCommand.ts create mode 100644 lib/extensions/discord-akairo/BushCommandHandler.ts create mode 100644 lib/extensions/discord-akairo/BushInhibitor.ts create mode 100644 lib/extensions/discord-akairo/BushInhibitorHandler.ts create mode 100644 lib/extensions/discord-akairo/BushListener.ts create mode 100644 lib/extensions/discord-akairo/BushListenerHandler.ts create mode 100644 lib/extensions/discord-akairo/BushTask.ts create mode 100644 lib/extensions/discord-akairo/BushTaskHandler.ts create mode 100644 lib/extensions/discord-akairo/SlashMessage.ts create mode 100644 lib/extensions/discord.js/BushClientEvents.ts create mode 100644 lib/extensions/discord.js/ExtendedGuild.ts create mode 100644 lib/extensions/discord.js/ExtendedGuildMember.ts create mode 100644 lib/extensions/discord.js/ExtendedMessage.ts create mode 100644 lib/extensions/discord.js/ExtendedUser.ts create mode 100644 lib/extensions/global.ts create mode 100644 lib/index.ts create mode 100644 lib/models/BaseModel.ts create mode 100644 lib/models/instance/ActivePunishment.ts create mode 100644 lib/models/instance/Guild.ts create mode 100644 lib/models/instance/Highlight.ts create mode 100644 lib/models/instance/Level.ts create mode 100644 lib/models/instance/ModLog.ts create mode 100644 lib/models/instance/Reminder.ts create mode 100644 lib/models/instance/StickyRole.ts create mode 100644 lib/models/shared/Global.ts create mode 100644 lib/models/shared/GuildCount.ts create mode 100644 lib/models/shared/MemberCount.ts create mode 100644 lib/models/shared/Shared.ts create mode 100644 lib/models/shared/Stat.ts create mode 100644 lib/tsconfig.json create mode 100644 lib/types/BushInspectOptions.ts create mode 100644 lib/types/CodeBlockLang.ts create mode 100644 lib/utils/AllowedMentions.ts create mode 100644 lib/utils/Arg.ts create mode 100644 lib/utils/BushClientUtils.ts create mode 100644 lib/utils/BushConstants.ts create mode 100644 lib/utils/BushLogger.ts create mode 100644 lib/utils/BushUtils.ts create mode 100644 lib/utils/Format.ts create mode 100644 lib/utils/Minecraft.ts create mode 100644 lib/utils/Minecraft_Test.ts create mode 100644 misc/test.js create mode 100644 misc/test.png create mode 100644 misc/tooltips.nnb delete mode 100644 src/arguments/abbreviatedNumber.ts delete mode 100644 src/arguments/contentWithDuration.ts delete mode 100644 src/arguments/discordEmoji.ts delete mode 100644 src/arguments/duration.ts delete mode 100644 src/arguments/durationSeconds.ts delete mode 100644 src/arguments/globalUser.ts delete mode 100644 src/arguments/index.ts delete mode 100644 src/arguments/messageLink.ts delete mode 100644 src/arguments/permission.ts delete mode 100644 src/arguments/roleWithDuration.ts delete mode 100644 src/arguments/snowflake.ts delete mode 100644 src/arguments/tinyColor.ts delete mode 100644 src/lib/badlinks.ts delete mode 100644 src/lib/badwords.ts delete mode 100644 src/lib/common/AutoMod.ts delete mode 100644 src/lib/common/ButtonPaginator.ts delete mode 100644 src/lib/common/ConfirmationPrompt.ts delete mode 100644 src/lib/common/DeleteButton.ts delete mode 100644 src/lib/common/HighlightManager.ts delete mode 100644 src/lib/common/Sentry.ts delete mode 100644 src/lib/common/tags.ts delete mode 100644 src/lib/common/typings/BushInspectOptions.ts delete mode 100644 src/lib/common/typings/CodeBlockLang.ts delete mode 100644 src/lib/common/util/Arg.ts delete mode 100644 src/lib/common/util/Format.ts delete mode 100644 src/lib/common/util/Minecraft.ts delete mode 100644 src/lib/common/util/Minecraft_Test.ts delete mode 100644 src/lib/common/util/Moderation.ts delete mode 100644 src/lib/extensions/discord-akairo/BushArgumentTypeCaster.ts delete mode 100644 src/lib/extensions/discord-akairo/BushClient.ts delete mode 100644 src/lib/extensions/discord-akairo/BushCommand.ts delete mode 100644 src/lib/extensions/discord-akairo/BushCommandHandler.ts delete mode 100644 src/lib/extensions/discord-akairo/BushInhibitor.ts delete mode 100644 src/lib/extensions/discord-akairo/BushInhibitorHandler.ts delete mode 100644 src/lib/extensions/discord-akairo/BushListener.ts delete mode 100644 src/lib/extensions/discord-akairo/BushListenerHandler.ts delete mode 100644 src/lib/extensions/discord-akairo/BushTask.ts delete mode 100644 src/lib/extensions/discord-akairo/BushTaskHandler.ts delete mode 100644 src/lib/extensions/discord-akairo/SlashMessage.ts delete mode 100644 src/lib/extensions/discord.js/BushClientEvents.ts delete mode 100644 src/lib/extensions/discord.js/ExtendedGuild.ts delete mode 100644 src/lib/extensions/discord.js/ExtendedGuildMember.ts delete mode 100644 src/lib/extensions/discord.js/ExtendedMessage.ts delete mode 100644 src/lib/extensions/discord.js/ExtendedUser.ts delete mode 100644 src/lib/extensions/global.ts delete mode 100644 src/lib/index.ts delete mode 100644 src/lib/models/BaseModel.ts delete mode 100644 src/lib/models/instance/ActivePunishment.ts delete mode 100644 src/lib/models/instance/Guild.ts delete mode 100644 src/lib/models/instance/Highlight.ts delete mode 100644 src/lib/models/instance/Level.ts delete mode 100644 src/lib/models/instance/ModLog.ts delete mode 100644 src/lib/models/instance/Reminder.ts delete mode 100644 src/lib/models/instance/StickyRole.ts delete mode 100644 src/lib/models/shared/Global.ts delete mode 100644 src/lib/models/shared/GuildCount.ts delete mode 100644 src/lib/models/shared/MemberCount.ts delete mode 100644 src/lib/models/shared/Shared.ts delete mode 100644 src/lib/models/shared/Stat.ts delete mode 100644 src/lib/utils/AllowedMentions.ts delete mode 100644 src/lib/utils/BushCache.ts delete mode 100644 src/lib/utils/BushClientUtils.ts delete mode 100644 src/lib/utils/BushConstants.ts delete mode 100644 src/lib/utils/BushLogger.ts delete mode 100644 src/lib/utils/BushUtils.ts delete mode 100644 src/lib/utils/CanvasProgressBar.ts create mode 100644 src/listeners/automod/automodCreate.ts create mode 100644 src/listeners/automod/automodUpdate.ts create mode 100644 src/listeners/automod/memberAutomod.ts create mode 100644 src/listeners/automod/presenceAutomod.ts delete mode 100644 src/listeners/message/automodCreate.ts delete mode 100644 src/listeners/message/automodUpdate.ts create mode 100644 src/tsconfig.json delete mode 100644 test.js delete mode 100644 test.png delete mode 100644 tooltips.nnb create mode 100644 tsconfig.base.json diff --git a/.eslintrc.cjs b/.eslintrc.cjs index f62abfd..d246897 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -131,7 +131,7 @@ module.exports = { project: './tsconfig.eslint.json' }, plugins: ['@typescript-eslint', 'deprecation', 'import'], - ignorePatterns: ['dist'], + ignorePatterns: ['dist', 'node_modules'], rules: { 'no-return-await': 'off', '@typescript-eslint/no-empty-interface': 'warn', diff --git a/.gitignore b/.gitignore index 1ce2d89..2ec4b06 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ src/config/options.ts config/options.ts src/lib/badlinks-secret.ts +lib/badlinks-secret.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index bf4dd62..d86374b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,9 +6,8 @@ "**/CVS": true, "**/.DS_Store": true, "dist": false, - ".pnp.js": true, - "**/node_modules": true, - "**/dist/**/*.js.map": true + ".pnp.js": false, + "**/node_modules": true }, "javascript.preferences.importModuleSpecifier": "project-relative", "typescript.preferences.importModuleSpecifier": "project-relative", diff --git a/config/tsconfig.json b/config/tsconfig.json new file mode 100644 index 0000000..46b2d15 --- /dev/null +++ b/config/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../dist/config" + }, + "files": ["./Config.ts", "example-options.ts", "options.ts"] +} diff --git a/lib/arguments/abbreviatedNumber.ts b/lib/arguments/abbreviatedNumber.ts new file mode 100644 index 0000000..a7d8ce5 --- /dev/null +++ b/lib/arguments/abbreviatedNumber.ts @@ -0,0 +1,13 @@ +import type { BushArgumentTypeCaster } from '#lib'; +import assert from 'assert/strict'; +import numeral from 'numeral'; +assert(typeof numeral === 'function'); + +export const abbreviatedNumber: BushArgumentTypeCaster = (_, phrase) => { + if (!phrase) return null; + const num = numeral(phrase?.toLowerCase()).value(); + + if (typeof num !== 'number' || isNaN(num)) return null; + + return num; +}; diff --git a/lib/arguments/contentWithDuration.ts b/lib/arguments/contentWithDuration.ts new file mode 100644 index 0000000..0efba39 --- /dev/null +++ b/lib/arguments/contentWithDuration.ts @@ -0,0 +1,5 @@ +import { parseDuration, type BushArgumentTypeCaster, type ParsedDuration } from '#lib'; + +export const contentWithDuration: BushArgumentTypeCaster> = async (_, phrase) => { + return parseDuration(phrase); +}; diff --git a/lib/arguments/discordEmoji.ts b/lib/arguments/discordEmoji.ts new file mode 100644 index 0000000..92d6502 --- /dev/null +++ b/lib/arguments/discordEmoji.ts @@ -0,0 +1,14 @@ +import { regex, type BushArgumentTypeCaster } from '#lib'; +import type { Snowflake } from 'discord.js'; + +export const discordEmoji: BushArgumentTypeCaster = (_, phrase) => { + if (!phrase) return null; + const validEmoji: RegExpExecArray | null = regex.discordEmoji.exec(phrase); + if (!validEmoji || !validEmoji.groups) return null; + return { name: validEmoji.groups.name, id: validEmoji.groups.id }; +}; + +export interface DiscordEmojiInfo { + name: string; + id: Snowflake; +} diff --git a/lib/arguments/duration.ts b/lib/arguments/duration.ts new file mode 100644 index 0000000..09dd3d5 --- /dev/null +++ b/lib/arguments/duration.ts @@ -0,0 +1,5 @@ +import { parseDuration, type BushArgumentTypeCaster } from '#lib'; + +export const duration: BushArgumentTypeCaster = (_, phrase) => { + return parseDuration(phrase).duration; +}; diff --git a/lib/arguments/durationSeconds.ts b/lib/arguments/durationSeconds.ts new file mode 100644 index 0000000..d8d6749 --- /dev/null +++ b/lib/arguments/durationSeconds.ts @@ -0,0 +1,6 @@ +import { parseDuration, type BushArgumentTypeCaster } from '#lib'; + +export const durationSeconds: BushArgumentTypeCaster = (_, phrase) => { + phrase += 's'; + return parseDuration(phrase).duration; +}; diff --git a/lib/arguments/globalUser.ts b/lib/arguments/globalUser.ts new file mode 100644 index 0000000..4324aa9 --- /dev/null +++ b/lib/arguments/globalUser.ts @@ -0,0 +1,7 @@ +import type { BushArgumentTypeCaster } from '#lib'; +import type { User } from 'discord.js'; + +// resolve non-cached users +export const globalUser: BushArgumentTypeCaster> = async (message, phrase) => { + return message.client.users.resolve(phrase) ?? (await message.client.users.fetch(`${phrase}`).catch(() => null)); +}; diff --git a/lib/arguments/index.ts b/lib/arguments/index.ts new file mode 100644 index 0000000..eebf0a2 --- /dev/null +++ b/lib/arguments/index.ts @@ -0,0 +1,10 @@ +export * from './abbreviatedNumber.js'; +export * from './contentWithDuration.js'; +export * from './discordEmoji.js'; +export * from './duration.js'; +export * from './durationSeconds.js'; +export * from './globalUser.js'; +export * from './messageLink.js'; +export * from './permission.js'; +export * from './roleWithDuration.js'; +export * from './snowflake.js'; diff --git a/lib/arguments/messageLink.ts b/lib/arguments/messageLink.ts new file mode 100644 index 0000000..c95e42d --- /dev/null +++ b/lib/arguments/messageLink.ts @@ -0,0 +1,20 @@ +import { BushArgumentTypeCaster, regex } from '#lib'; +import type { Message } from 'discord.js'; + +export const messageLink: BushArgumentTypeCaster> = async (message, phrase) => { + const match = new RegExp(regex.messageLink).exec(phrase); + if (!match || !match.groups) return null; + + const { guild_id, channel_id, message_id } = match.groups; + + if (!guild_id || !channel_id || message_id) return null; + + const guild = message.client.guilds.cache.get(guild_id); + if (!guild) return null; + + const channel = guild.channels.cache.get(channel_id); + if (!channel || (!channel.isTextBased() && !channel.isThread())) return null; + + const msg = await channel.messages.fetch(message_id).catch(() => null); + return msg; +}; diff --git a/lib/arguments/permission.ts b/lib/arguments/permission.ts new file mode 100644 index 0000000..98bfe74 --- /dev/null +++ b/lib/arguments/permission.ts @@ -0,0 +1,12 @@ +import type { BushArgumentTypeCaster } from '#lib'; +import { PermissionFlagsBits, type PermissionsString } from 'discord.js'; + +export const permission: BushArgumentTypeCaster = (_, phrase) => { + if (!phrase) return null; + phrase = phrase.toUpperCase().replace(/ /g, '_'); + if (!(phrase in PermissionFlagsBits)) { + return null; + } else { + return phrase as PermissionsString; + } +}; diff --git a/lib/arguments/roleWithDuration.ts b/lib/arguments/roleWithDuration.ts new file mode 100644 index 0000000..b97f205 --- /dev/null +++ b/lib/arguments/roleWithDuration.ts @@ -0,0 +1,17 @@ +import { Arg, BushArgumentTypeCaster, parseDuration } from '#lib'; +import type { Role } from 'discord.js'; + +export const roleWithDuration: BushArgumentTypeCaster> = async (message, phrase) => { + // eslint-disable-next-line prefer-const + let { duration, content } = parseDuration(phrase); + if (content === null || content === undefined) return null; + content = content.trim(); + const role = await Arg.cast('role', message, content); + if (!role) return null; + return { duration, role }; +}; + +export interface RoleWithDuration { + duration: number | null; + role: Role | null; +} diff --git a/lib/arguments/snowflake.ts b/lib/arguments/snowflake.ts new file mode 100644 index 0000000..b98a20f --- /dev/null +++ b/lib/arguments/snowflake.ts @@ -0,0 +1,8 @@ +import { BushArgumentTypeCaster, regex } from '#lib'; +import type { Snowflake } from 'discord.js'; + +export const snowflake: BushArgumentTypeCaster = (_, phrase) => { + if (!phrase) return null; + if (regex.snowflake.test(phrase)) return phrase; + return null; +}; diff --git a/lib/arguments/tinyColor.ts b/lib/arguments/tinyColor.ts new file mode 100644 index 0000000..148c078 --- /dev/null +++ b/lib/arguments/tinyColor.ts @@ -0,0 +1,10 @@ +import type { BushArgumentTypeCaster } from '#lib'; +import assert from 'assert/strict'; +import tinycolorModule from 'tinycolor2'; +assert(tinycolorModule); + +export const tinyColor: BushArgumentTypeCaster = (_message, phrase) => { + // if the phase is a number it converts it to hex incase it could be representing a color in decimal + const newPhase = isNaN(phrase as any) ? phrase : `#${Number(phrase).toString(16)}`; + return tinycolorModule(newPhase).isValid() ? newPhase : null; +}; diff --git a/lib/automod/AutomodShared.ts b/lib/automod/AutomodShared.ts new file mode 100644 index 0000000..5d031d0 --- /dev/null +++ b/lib/automod/AutomodShared.ts @@ -0,0 +1,310 @@ +import { + ActionRowBuilder, + ButtonBuilder, + ButtonInteraction, + ButtonStyle, + GuildMember, + Message, + 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'; + +/** + * Handles shared auto moderation functionality. + */ +export abstract class Automod { + /** + * Whether or not a punishment has already been given to the user + */ + protected punished = false; + + /** + * @param member The guild member that the automod is checking + */ + protected constructor(protected readonly member: GuildMember) {} + + /** + * The user + */ + protected get user() { + return this.member.user; + } + + /** + * The client instance + */ + protected get client() { + return this.member.client; + } + + /** + * The guild member that the automod is checking + */ + protected get guild() { + return this.member.guild; + } + + /** + * Whether or not the member should be immune to auto moderation + */ + protected get isImmune() { + if (this.member.user.isOwner()) return true; + if (this.member.guild.ownerId === this.member.id) return true; + if (this.member.permissions.has('Administrator')) return true; + + return false; + } + + protected buttons(userId: Snowflake, reason: string, undo = true): ActionRowBuilder { + const row = new ActionRowBuilder().addComponents([ + new ButtonBuilder({ + style: ButtonStyle.Danger, + label: 'Ban User', + customId: `automod;ban;${userId};${reason}` + }) + ]); + + if (undo) { + row.addComponents( + new ButtonBuilder({ + style: ButtonStyle.Success, + label: 'Unmute User', + customId: `automod;unmute;${userId}` + }) + ); + } + + return row; + } + + protected logColor(severity: Severity) { + switch (severity) { + case Severity.DELETE: + return colors.lightGray; + case Severity.WARN: + return colors.yellow; + case Severity.TEMP_MUTE: + return colors.orange; + case Severity.PERM_MUTE: + return colors.red; + } + throw new Error(`Unknown severity: ${severity}`); + } + + /** + * Checks if any of the words provided are in the message + * @param words The words to check for + * @returns The blacklisted words found in the message + */ + protected checkWords(words: BadWordDetails[], str: string): BadWordDetails[] { + if (words.length === 0) return []; + + const matchedWords: BadWordDetails[] = []; + for (const word of words) { + if (word.regex) { + if (new RegExp(word.match).test(this.format(word.match, word))) { + matchedWords.push(word); + } + } else { + if (this.format(str, word).includes(this.format(word.match, word))) { + matchedWords.push(word); + } + } + } + return matchedWords; + } + + /** + * Format a string according to the word options + * @param string The string to format + * @param wordOptions The word options to format with + * @returns The formatted string + */ + protected format(string: string, wordOptions: BadWordDetails) { + const temp = wordOptions.ignoreCapitalization ? string.toLowerCase() : string; + return wordOptions.ignoreSpaces ? temp.replace(/ /g, '') : temp; + } + + /** + * Handles the auto moderation + */ + protected abstract handle(): Promise; +} + +/** + * Handles the ban button in the automod log. + * @param interaction The button interaction. + */ +export async function handleAutomodInteraction(interaction: ButtonInteraction) { + if (!interaction.memberPermissions?.has(PermissionFlagsBits.BanMembers)) + return interaction.reply({ + content: `${emojis.error} You are missing the **Ban Members** permission.`, + ephemeral: true + }); + const [action, userId, reason] = interaction.customId.replace('automod;', '').split(';') as ['ban' | 'unmute', string, string]; + + if (!(['ban', 'unmute'] as const).includes(action)) throw new TypeError(`Invalid automod button action: ${action}`); + + const victim = await interaction.guild!.members.fetch(userId).catch(() => null); + const moderator = + interaction.member instanceof GuildMember ? interaction.member : await interaction.guild!.members.fetch(interaction.user.id); + + switch (action) { + case 'ban': { + if (!interaction.guild?.members.me?.permissions.has('BanMembers')) + return interaction.reply({ + content: `${emojis.error} I do not have permission to ${action} members.`, + ephemeral: true + }); + + const check = victim ? await Moderation.permissionCheck(moderator, victim, 'ban', true) : true; + if (check !== true) return interaction.reply({ content: check, ephemeral: true }); + + const result = await interaction.guild?.bushBan({ + user: userId, + reason, + moderator: interaction.user.id, + evidence: (interaction.message as Message).url ?? undefined + }); + + const victimUserFormatted = (await interaction.client.utils.resolveNonCachedUser(userId))?.tag ?? userId; + + const content = (() => { + if (result === unmuteResponse.SUCCESS) { + return `${emojis.success} Successfully banned ${Format.input(victimUserFormatted)}.`; + } else if (result === unmuteResponse.DM_ERROR) { + return `${emojis.warn} Banned ${Format.input(victimUserFormatted)} however I could not send them a dm.`; + } else { + return `${emojis.error} Could not ban ${Format.input(victimUserFormatted)}: \`${result}\` .`; + } + })(); + + return interaction.reply({ + content: content, + ephemeral: true + }); + } + + case 'unmute': { + if (!victim) + return interaction.reply({ + content: `${emojis.error} Cannot find member, they may have left the server.`, + ephemeral: true + }); + + if (!interaction.guild) + return interaction.reply({ + content: `${emojis.error} This is weird, I don't seem to be in the server...`, + ephemeral: true + }); + + const check = await Moderation.permissionCheck(moderator, victim, 'unmute', true); + 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 }); + + const result = await victim.bushUnmute({ + reason, + moderator: interaction.member as GuildMember, + evidence: (interaction.message as Message).url ?? undefined + }); + + const victimUserFormatted = victim.user.tag; + + const content = (() => { + if (result === unmuteResponse.SUCCESS) { + return `${emojis.success} Successfully unmuted ${Format.input(victimUserFormatted)}.`; + } else if (result === unmuteResponse.DM_ERROR) { + return `${emojis.warn} Unmuted ${Format.input(victimUserFormatted)} however I could not send them a dm.`; + } else { + return `${emojis.error} Could not unmute ${Format.input(victimUserFormatted)}: \`${result}\` .`; + } + })(); + + return interaction.reply({ + content: content, + ephemeral: true + }); + } + } +} + +/** + * The severity of the blacklisted word + */ +export const enum Severity { + /** + * Delete message + */ + DELETE, + + /** + * Delete message and warn user + */ + WARN, + + /** + * Delete message and mute user for 15 minutes + */ + TEMP_MUTE, + + /** + * Delete message and mute user permanently + */ + PERM_MUTE +} + +/** + * Details about a blacklisted word + */ +export interface BadWordDetails { + /** + * The word that is blacklisted + */ + match: string; + + /** + * The severity of the word + */ + severity: Severity | 1 | 2 | 3; + + /** + * Whether or not to ignore spaces when checking for the word + */ + ignoreSpaces: boolean; + + /** + * Whether or not to ignore case when checking for the word + */ + ignoreCapitalization: boolean; + + /** + * The reason that this word is blacklisted (used for the punishment reason) + */ + reason: string; + + /** + * Whether or not the word is regex + * @default false + */ + regex: boolean; + + /** + * Whether to also check a user's status and username for the phrase + * @default false + */ + userInfo: boolean; +} + +/** + * Blacklisted words mapped to their details + */ +export interface BadWords { + [category: string]: BadWordDetails[]; +} diff --git a/lib/automod/MemberAutomod.ts b/lib/automod/MemberAutomod.ts new file mode 100644 index 0000000..6f71457 --- /dev/null +++ b/lib/automod/MemberAutomod.ts @@ -0,0 +1,72 @@ +import { stripIndent } from '#tags'; +import { EmbedBuilder, GuildMember } from 'discord.js'; +import { Automod, BadWordDetails } from './AutomodShared.js'; + +export class MemberAutomod extends Automod { + /** + * @param member The member that the automod is checking + */ + public constructor(member: GuildMember) { + super(member); + + if (member.id === member.client.user?.id) return; + + void this.handle(); + } + + protected async handle(): Promise { + if (this.member.user.bot) return; + + const badWordsRaw = Object.values(this.client.utils.getShared('badWords')).flat(); + const customAutomodPhrases = (await this.guild.getSetting('autoModPhases')) ?? []; + + const phrases = [...badWordsRaw, ...customAutomodPhrases].filter((p) => p.userInfo); + + const result: BadWordDetails[] = []; + + const str = `${this.member.user.username}${this.member.nickname ? `\n${this.member.nickname}` : ''}`; + const check = this.checkWords(phrases, str); + if (check.length > 0) { + result.push(...check); + } + + if (result.length > 0) { + const highestOffense = result.sort((a, b) => b.severity - a.severity)[0]; + await this.logMessage(highestOffense, result, str); + } + } + + /** + * Log an automod infraction to the guild's specified automod log channel + * @param highestOffense The highest severity word found in the message + * @param offenses The other offenses that were also matched in the message + */ + protected async logMessage(highestOffense: BadWordDetails, offenses: BadWordDetails[], str: string) { + void this.client.console.info( + 'MemberAutomod', + `Detected a severity <<${highestOffense.severity}>> automod phrase in <<${this.user.tag}>>'s (<<${this.user.id}>>) username or nickname in <<${this.guild.name}>>` + ); + + const color = this.logColor(highestOffense.severity); + + await this.guild.sendLogChannel('automod', { + embeds: [ + new EmbedBuilder() + .setTitle(`[Severity ${highestOffense.severity}] Automoderated User Info Detected`) + .setDescription( + stripIndent` + **User:** ${this.user} (${this.user.tag}) + **Blacklisted Words:** ${offenses.map((o) => `\`${o.match}\``).join(', ')}` + ) + .addFields({ + name: 'Info', + value: `${await this.client.utils.codeblock(str, 1024)}` + }) + .setColor(color) + .setTimestamp() + .setAuthor({ name: this.user.tag, url: this.user.displayAvatarURL() }) + ], + components: [this.buttons(this.user.id, highestOffense.reason, false)] + }); + } +} diff --git a/lib/automod/MessageAutomod.ts b/lib/automod/MessageAutomod.ts new file mode 100644 index 0000000..9673adf --- /dev/null +++ b/lib/automod/MessageAutomod.ts @@ -0,0 +1,286 @@ +import { stripIndent } from '#tags'; +import assert from 'assert/strict'; +import chalk from 'chalk'; +import { EmbedBuilder, GuildTextBasedChannel, PermissionFlagsBits, type Message } from 'discord.js'; +import { colors } from '../utils/BushConstants.js'; +import { format, formatError } from '../utils/BushUtils.js'; +import { Automod, BadWordDetails, Severity } from './AutomodShared.js'; + +/** + * Handles message auto moderation functionality. + */ +export class MessageAutomod extends Automod { + /** + * @param message The message to check and potentially perform automod actions on + */ + public constructor(private readonly message: Message) { + assert(message.member); + super(message.member); + + if (message.author.id === message.client.user?.id) return; + void this.handle(); + } + + /** + * Handles the auto moderation + */ + protected async handle() { + if (!this.message.inGuild()) return; + if (!(await this.guild.hasFeature('automod'))) return; + if (this.user.bot) return; + if (!this.message.member) return; + + traditional: { + if (this.isImmune) break traditional; + const badLinksArray = this.client.utils.getShared('badLinks'); + const badLinksSecretArray = this.client.utils.getShared('badLinksSecret'); + const badWordsRaw = this.client.utils.getShared('badWords'); + + const customAutomodPhrases = (await this.guild.getSetting('autoModPhases')) ?? []; + const uniqueLinks = [...new Set([...badLinksArray, ...badLinksSecretArray])]; + + const badLinks: BadWordDetails[] = uniqueLinks.map((link) => ({ + match: link, + severity: Severity.PERM_MUTE, + ignoreSpaces: false, + ignoreCapitalization: true, + reason: 'malicious link', + regex: false, + userInfo: false + })); + + const parsedBadWords = Object.values(badWordsRaw).flat(); + + const result = this.checkWords( + [ + ...customAutomodPhrases, + ...((await this.guild.hasFeature('excludeDefaultAutomod')) ? [] : parsedBadWords), + ...((await this.guild.hasFeature('excludeAutomodScamLinks')) ? [] : badLinks) + ], + this.message.content + ); + + if (result.length === 0) break traditional; + + const highestOffense = result.sort((a, b) => b.severity - a.severity)[0]; + + if (highestOffense.severity === undefined || highestOffense.severity === null) { + void this.guild.sendLogChannel('error', { + embeds: [ + { + title: 'AutoMod Error', + description: `Unable to find severity information for ${format.inlineCode(highestOffense.match)}`, + color: colors.error + } + ] + }); + } else { + this.punish(highestOffense); + void this.logMessage(highestOffense, result); + } + } + + other: { + if (this.isImmune) break other; + if (!this.punished && (await this.guild.hasFeature('delScamMentions'))) void this.checkScamMentions(); + } + + if (!this.punished && (await this.guild.hasFeature('perspectiveApi'))) void this.checkPerspectiveApi(); + } + + /** + * If the message contains '@everyone' or '@here' and it contains a common scam phrase, it will be deleted + * @returns + */ + protected async checkScamMentions() { + const includes = (c: string) => this.message.content.toLocaleLowerCase().includes(c); + if (!includes('@everyone') && !includes('@here')) return; + + // It would be bad if we deleted a message that actually pinged @everyone or @here + if ( + this.member.permissionsIn(this.message.channelId).has(PermissionFlagsBits.MentionEveryone) || + this.message.mentions.everyone + ) + return; + + if ( + includes('steam') || + includes('www.youtube.com') || + includes('youtu.be') || + includes('nitro') || + includes('1 month') || + includes('3 months') || + includes('personalize your profile') || + includes('even more') || + includes('xbox and discord') || + includes('left over') || + includes('check this lol') || + includes('airdrop') + ) { + const color = this.logColor(Severity.PERM_MUTE); + this.punish({ severity: Severity.TEMP_MUTE, reason: 'everyone mention and scam phrase' } as BadWordDetails); + void this.guild!.sendLogChannel('automod', { + embeds: [ + new EmbedBuilder() + .setTitle(`[Severity ${Severity.TEMP_MUTE}] Mention Scam Deleted`) + .setDescription( + stripIndent` + **User:** ${this.user} (${this.user.tag}) + **Sent From:** <#${this.message.channel.id}> [Jump to context](${this.message.url})` + ) + .addFields({ + name: 'Message Content', + value: `${await this.client.utils.codeblock(this.message.content, 1024)}` + }) + .setColor(color) + .setTimestamp() + ], + components: [this.buttons(this.user.id, 'everyone mention and scam phrase')] + }); + } + } + + protected async checkPerspectiveApi() { + return; + if (!this.client.config.isDevelopment) return; + + if (!this.message.content) return; + this.client.perspective.comments.analyze( + { + key: this.client.config.credentials.perspectiveApiKey, + resource: { + comment: { + text: this.message.content + }, + requestedAttributes: { + TOXICITY: {}, + SEVERE_TOXICITY: {}, + IDENTITY_ATTACK: {}, + INSULT: {}, + PROFANITY: {}, + THREAT: {}, + SEXUALLY_EXPLICIT: {}, + FLIRTATION: {} + } + } + }, + (err: any, response: any) => { + if (err) return console.log(err?.message); + + const normalize = (val: number, min: number, max: number) => (val - min) / (max - min); + + const color = (val: number) => { + if (val >= 0.5) { + const x = 194 - Math.round(normalize(val, 0.5, 1) * 194); + return chalk.rgb(194, x, 0)(val); + } else { + const x = Math.round(normalize(val, 0, 0.5) * 194); + return chalk.rgb(x, 194, 0)(val); + } + }; + + console.log(chalk.cyan(this.message.content)); + Object.entries(response.data.attributeScores) + .sort(([a], [b]) => a.localeCompare(b)) + .forEach(([key, value]: any[]) => console.log(chalk.white(key), color(value.summaryScore.value))); + } + ); + } + + /** + * Punishes the user based on the severity of the offense + * @param highestOffense The highest offense to punish the user for + * @returns The color of the embed that the log should, based on the severity of the offense + */ + protected punish(highestOffense: BadWordDetails) { + switch (highestOffense.severity) { + case Severity.DELETE: { + void this.message.delete().catch((e) => deleteError.bind(this, e)); + this.punished = true; + break; + } + case Severity.WARN: { + void this.message.delete().catch((e) => deleteError.bind(this, e)); + void this.member.bushWarn({ + moderator: this.guild!.members.me!, + reason: `[Automod] ${highestOffense.reason}` + }); + this.punished = true; + break; + } + case Severity.TEMP_MUTE: { + void this.message.delete().catch((e) => deleteError.bind(this, e)); + void this.member.bushMute({ + moderator: this.guild!.members.me!, + reason: `[Automod] ${highestOffense.reason}`, + duration: 900_000 // 15 minutes + }); + this.punished = true; + break; + } + case Severity.PERM_MUTE: { + void this.message.delete().catch((e) => deleteError.bind(this, e)); + void this.member.bushMute({ + moderator: this.guild!.members.me!, + reason: `[Automod] ${highestOffense.reason}`, + duration: 0 // permanent + }); + this.punished = true; + break; + } + default: { + throw new Error(`Invalid severity: ${highestOffense.severity}`); + } + } + + async function deleteError(this: MessageAutomod, e: Error | any) { + void this.guild?.sendLogChannel('error', { + embeds: [ + { + title: 'Automod Error', + description: `Unable to delete triggered message.`, + fields: [{ name: 'Error', value: await this.client.utils.codeblock(`${formatError(e)}`, 1024, 'js', true) }], + color: colors.error + } + ] + }); + } + } + + /** + * Log an automod infraction to the guild's specified automod log channel + * @param highestOffense The highest severity word found in the message + * @param offenses The other offenses that were also matched in the message + */ + protected async logMessage(highestOffense: BadWordDetails, offenses: BadWordDetails[]) { + void this.client.console.info( + 'MessageAutomod', + `Severity <<${highestOffense.severity}>> action performed on <<${this.user.tag}>> (<<${this.user.id}>>) in <<#${ + (this.message.channel as GuildTextBasedChannel).name + }>> in <<${this.guild!.name}>>` + ); + + const color = this.logColor(highestOffense.severity); + + await this.guild!.sendLogChannel('automod', { + embeds: [ + new EmbedBuilder() + .setTitle(`[Severity ${highestOffense.severity}] Automod Action Performed`) + .setDescription( + stripIndent` + **User:** ${this.user} (${this.user.tag}) + **Sent From:** <#${this.message.channel.id}> [Jump to context](${this.message.url}) + **Blacklisted Words:** ${offenses.map((o) => `\`${o.match}\``).join(', ')}` + ) + .addFields({ + name: 'Message Content', + value: `${await this.client.utils.codeblock(this.message.content, 1024)}` + }) + .setColor(color) + .setTimestamp() + .setAuthor({ name: this.user.tag, url: this.user.displayAvatarURL() }) + ], + components: highestOffense.severity >= 2 ? [this.buttons(this.user.id, highestOffense.reason)] : undefined + }); + } +} diff --git a/lib/automod/PresenceAutomod.ts b/lib/automod/PresenceAutomod.ts new file mode 100644 index 0000000..70c66d6 --- /dev/null +++ b/lib/automod/PresenceAutomod.ts @@ -0,0 +1,85 @@ +import { stripIndent } from '#tags'; +import { EmbedBuilder, Presence } from 'discord.js'; +import { Automod, BadWordDetails } from './AutomodShared.js'; + +export class PresenceAutomod extends Automod { + /** + * @param presence The presence that the automod is checking + */ + public constructor(public readonly presence: Presence) { + super(presence.member!); + + if (presence.member!.id === presence.client.user?.id) return; + + void this.handle(); + } + + protected async handle(): Promise { + if (this.presence.member!.user.bot) return; + + const badWordsRaw = Object.values(this.client.utils.getShared('badWords')).flat(); + const customAutomodPhrases = (await this.guild.getSetting('autoModPhases')) ?? []; + + const phrases = [...badWordsRaw, ...customAutomodPhrases].filter((p) => p.userInfo); + + const result: BadWordDetails[] = []; + + const strings = []; + + for (const activity of this.presence.activities) { + const str = `${activity.name}${activity.details ? `\n${activity.details}` : ''}${ + activity.buttons.length > 0 ? `\n${activity.buttons.join('\n')}` : '' + }`; + const check = this.checkWords(phrases, str); + if (check.length > 0) { + result.push(...check); + strings.push(str); + } + } + + if (result.length > 0) { + const highestOffense = result.sort((a, b) => b.severity - a.severity)[0]; + await this.logMessage(highestOffense, result, strings); + } + } + + /** + * Log an automod infraction to the guild's specified automod log channel + * @param highestOffense The highest severity word found in the message + * @param offenses The other offenses that were also matched in the message + */ + protected async logMessage(highestOffense: BadWordDetails, offenses: BadWordDetails[], strings: string[]) { + void this.client.console.info( + 'PresenceAutomod', + `Detected a severity <<${highestOffense.severity}>> automod phrase in <<${this.user.tag}>>'s (<<${this.user.id}>>) presence in <<${this.guild.name}>>` + ); + + const color = this.logColor(highestOffense.severity); + + await this.guild.sendLogChannel('automod', { + embeds: [ + new EmbedBuilder() + .setTitle(`[Severity ${highestOffense.severity}] Automoderated Status Detected`) + .setDescription( + stripIndent` + **User:** ${this.user} (${this.user.tag}) + **Blacklisted Words:** ${offenses.map((o) => `\`${o.match}\``).join(', ')}` + ) + .addFields( + ( + await Promise.all( + strings.map(async (s) => ({ + name: 'Status', + value: `${await this.client.utils.codeblock(s, 1024)}` + })) + ) + ).slice(0, 25) + ) + .setColor(color) + .setTimestamp() + .setAuthor({ name: this.user.tag, url: this.user.displayAvatarURL() }) + ], + components: [this.buttons(this.user.id, highestOffense.reason, false)] + }); + } +} diff --git a/lib/badlinks.ts b/lib/badlinks.ts new file mode 100644 index 0000000..3b4cf3b --- /dev/null +++ b/lib/badlinks.ts @@ -0,0 +1,6930 @@ +/* Links in this file are treated as severity 3 offences. + +made in part possible by https://github.com/nacrt/SkyblockClient-REPO/blob/main/files/scamlinks.json */ +export default [ + "//iscord.gift", + "100cs.ru", + "100eshopdeals.com", + "101nitro.com", + "12mon.space", + "1nitro.club", + "2021cs.net.ru", + "2021ga.xyz", + "2021liss.ru", + "2021pn.ru", + "2021y.ru", + "2022p.ru", + "2022yg.com", + "2023g.com", + "23c7481e.hbrex.cn", + "2discord.ru", + "2faceteam.ml", + "3ds-security.xyz", + "3items4rocket.com", + "4drop.ru.com", + "academynaviagg.xyz", + "accountauthorization.xyz", + "acercup.com", + "ach2x.net.ru", + "achnavi.net.ru", + "acid-tournament.ru", + "affix-cup.click", + "affix-cup.link", + "affix-cup.ru", + "affix-sport.ru", + "affixesports.ru", + "affixsport.ru", + "afkskroll.ru", + "ahijeoir.ru", + "airdrop-discord.com", + "airdrop-discord.online", + "airdrop-discord.ru", + "airdrop-nitro.com", + "airdrops.tips", + "akellasport.me", + "aladdinhub.fun", + "alexandrkost.ru", + "alexs1.ru", + "alive-lives.ru", + "allskinz.xyz", + "alm-gaming.com", + "alone18.ru", + "alonemoly.ru", + "amaterasu.pp.ua", + "ano-skinspin.xyz", + "anomalygiveaways.pro", + "anomalyknifes.xyz", + "anomalyskin.xyz", + "anomalyskinz.xyz", + "anoskinzz.xyz", + "antibot.cc", + "aoeah.promo-codes.world", + "aoeah.shop", + "api.code2gether.cf", + "api.innovations-urfu.site", + "app-discord.com", + "app-discord.ru", + "app-nitro.com", + "application-discord.com", + "appnitro-discord.com", + "appnitro-discord.ru.com", + "appnitrodiscord.ru.com", + "apps-discord.org", + "apps-nitro.com", + "arik.pp.ua", + "asprod911.com", + "asstralissport.org.ru", + "astr-teem.net.ru", + "astr-teem.org.ru", + "astralis-gg.com", + "astralis.monster", + "astralis2.net.ru", + "astralis2.org.ru", + "astralisgift.fun", + "astrallis.net.ru", + "astrallis.org.ru", + "astralliscase.org.ru", + "astralteam.org.ru", + "astresports.xyz", + "atomicstore.ru", + "attaxtrade.com", + "aucryptohubs.com", + "authnet.cf", + "autumnbot.cloud", + "avitofast.ru", + "awirabigmoneyroll.xyz", + "awirabigmoneyrolls.xyz", + "azimovcase.tk", + "badge-team.ml", + "ball-chaser.xyz", + "bandycazez.xyz", + "bangbro.ru", + "battiefy.com", + "beast-cup.ru", + "beast-dr0p.ru", + "beast-winer.ru", + "belekevskeigames.xyz", + "berrygamble.com", + "best-cup.com", + "best-cup.ru", + "bestgeeknavi.ru", + "bestshopusaoffers.com", + "bestskins.org.ru", + "beststeam.gq", + "bestwatchstyle.com", + "beta.discorder.app", + "betadiscord.com", + "bets-cup.ru", + "big.org.ru", + "big.pp.ru", + "bigcsgo.pro", + "bigesports.ru", + "bigmoneyrollawira.xyz", + "bigs.monster", + "bigsports.xyz", + "bistripudel.xyz", + "bit-skins.ru", + "bitcoingenerator.cash", + "bitknife.xyz", + "bitskeansell.ru", + "bitskines.ru", + "blockmincnain.com", + "blocknimchain.com", + "blocksilcnain.com", + "blox.land", + "bloxpromo.com", + "blustcoin.com", + "board-nitro.com", + "bondikflas.xyz", + "bonusxcase.xyz", + "books-pash.org.ru", + "boost-discord.com", + "boost-nitro.com", + "boosted-nitro.com", + "boostnitro.com", + "boostnltro.com", + "bountyweek.com", + "box-surprisebynavi.net.ru", + "boxgolg.club", + "boxnode.ru", + "br0ken-fng.xyz", + "bracesports.ru", + "bro-skiils.net.ru", + "brokenfang-csgo.com", + "brokenfangpassfree.pp.ru", + "brokenfant.org.ru", + "brokentournament.xyz", + "bruteclub.ru", + "buff-market.ru", + "buffgames.ru", + "but-three.xyz", + "buxquick.com", + "buzz-cup.ru", + "bycdu.cam", + "bycsdu.cam", + "bysellers.xyz", + "c-you-mamont.ru", + "c2bit.online", + "c2bit.su", + "case-free.com", + "case-gift.com", + "case-give.com", + "case-magic.space", + "casecs.ru", + "casefire.fun", + "casekey.ru.com", + "casesdrop.ru", + "casesdrop.xyz", + "cash.org.ru", + "cash.pp.ru", + "cashcsgo.ru", + "cashout.monster", + "cashy.monster", + "cassesoma.ru", + "cave-nitro.com", + "cawanmei.ru", + "cawanmei99.ru", + "ccomstimoon.org.ru", + "cgsell.ru", + "cgskinky.xyz", + "chainexplo.com", + "challengeme.in", + "challengeme.vip", + "challengme.ru", + "chance-stem.ru", + "chinchopa.pp.ua", + "circus-shop.ru", + "cis-fastcup.ru", + "cis-rankig.ru", + "cityofmydream.pp.ua", + "claim.robuxat.com", + "claimgifts.shop", + "clan-big.ru", + "classic-nitro.com", + "claud9.xyz", + "clck.ru", + "click-mell.pp.ru", + "cliscord-gift.ru.com", + "cllscordapp.fun", + "cloud9.ru.com", + "cloud9team.space", + "cloudeskins.com", + "cloudfox.one", + "cloudteam9.com", + "clove-nitro.com", + "cmepure.com", + "cmskillcup.com", + "cod3r0bux.pw", + "cointradebtc.com", + "comboline.xyz", + "comdiscord.com", + "come-nitro.com", + "communitytradeoffer.com.ru", + "communitytradeoffer.com", + "communltydrop.pp.ua", + "communltyguard.pp.ua", + "comsteamcommunity.com", + "contact-infoservice.com", + "contralav.ru", + "contralav.xyz", + "coolcools.xyz", + "cooldrop.monster", + "copyrightbusinessgroup.com", + "copyrightbussinessgroup.com", + "copyrighthelpbusiness.org", + "cose-lore.ru", + "counter-stricke.ru", + "counter-strlke.site", + "counterbase.ru.com", + "counterpaid.xyz", + "counterspin.top", + "counterstrik.xyz", + "counterstrikegift.xyz", + "cpanel.copyrighthelpbusiness.org", + "cpbldi.com", + "cpp-discord.com", + "crazy-soom.org.ru", + "crazypage.me", + "creack.tk", + "creditscpfree.website", + "crosflah.online", + "crustalcup.ga", + "cs-activit.xyz", + "cs-astria.xyz", + "cs-beast.xyz", + "cs-betway.xyz", + "cs-boom.org.ru", + "cs-cool.net.ru", + "cs-dark.org.ru", + "cs-dump.org.ru", + "cs-esports.link", + "cs-exeword.xyz", + "cs-fail.ru.com", + "cs-fall.ru.com", + "cs-gameis.ru", + "cs-gorun.ru.com", + "cs-grun.ru.com", + "cs-incursed.xyz", + "cs-legend.xyz", + "cs-lucky.xyz", + "cs-moneyy.ru", + "cs-navigiveaway.ru", + "cs-open.link", + "cs-pill.xyz", + "cs-play.org.ru", + "cs-prizeskins.xyz", + "cs-prizeskinz.xyz", + "cs-riptide.com", + "cs-riptide.ru", + "cs-riptide.xyz", + "cs-simpleroll.xyz", + "cs-skins.link", + "cs-skinz.xyz", + "cs-smoke.xyz", + "cs-spinz.xyz", + "cs-toom.pp.ru", + "cs-tournament.link", + "cs-victory.xyz", + "cs11go.space", + "cs4real.pp.ua", + "cs500go.com", + "csallskin.xyz", + "csbuyskins.in", + "cschanse.ru", + "cschecker.ru", + "cscoat.eu", + "cscodes.ru", + "csfair.pp.ua", + "csfix.me", + "csfreedom.me", + "csfreesklns.ru.com", + "csgameik.ru", + "csgdrop.ru", + "csgfocusa.ru", + "csggolg.ru", + "csgif.org.ru", + "csgift.fun", + "csgo-analyst.com", + "csgo-battle.ru", + "csgo-cash.eu", + "csgo-cup.ru", + "csgo-cyber.link", + "csgo-dym.ru", + "csgo-fute.net.ru", + "csgo-game-steam.ru", + "csgo-games.xyz", + "csgo-gamesteam.ru", + "csgo-gifts.com", + "csgo-lute.net.ru", + "csgo-market.ru.com", + "csgo-pell.org.ru", + "csgo-riptide.ru", + "csgo-run.info", + "csgo-run.site", + "csgo-sports.com", + "csgo-st.ru", + "csgo-steam-game.ru", + "csgo-steam-good.ru", + "csgo-steamanalyst.net", + "csgo-steamgame.ru", + "csgo-steamplay.ru", + "csgo-store-steam.ru", + "csgo-storesteam.ru", + "csgo-swapskin.com", + "csgo-trade.net", + "csgo-up.com", + "csgo-z.com", + "csgo.ghservers.cl", + "csgo2021.ru", + "csgo4cases.fun", + "csgobb.xyz", + "csgobccp.ru", + "csgobeats.com", + "csgobelieve.ru", + "csgocase.monster", + "csgocase.one", + "csgocases.monster", + "csgocashs.com", + "csgocheck.ru.com", + "csgocheck.ru", + "csgochinasteam.ru", + "csgocj-steam.work", + "csgocnfocuss.ru", + "csgocompetive.com", + "csgocup.ru", + "csgocupp.ru.com", + "csgocybersport.ru.com", + "csgodetails.info", + "csgodirect.xyz", + "csgodreamer.com", + "csgodrops.monster", + "csgodrs.com", + "csgoeasywin.ru.com", + "csgoelite.xyz", + "csgoencup.com", + "csgoevent.xyz", + "csgofast.xyz", + "csgoflash.net.ru", + "csgofocusc.xyz", + "csgogame-steam.ru", + "csgoganeak.ru", + "csgoganeik.ru", + "csgogf01.xyz", + "csgogf02.xyz", + "csgogf03.xyz", + "csgogf04.xyz", + "csgogf05.xyz", + "csgogf06.xyz", + "csgogf07.xyz", + "csgogf12.xyz", + "csgogf13.xyz", + "csgogf14.xyz", + "csgogf15.xyz", + "csgogift25.xyz", + "csgogift26.xyz", + "csgogift34.xyz", + "csgogift43.xyz", + "csgogift44.xyz", + "csgogift45.xyz", + "csgogift47.xyz", + "csgogift49.xyz", + "csgogift50.xyz", + "csgogift51.xyz", + "csgogift55.xyz", + "csgogift56.xyz", + "csgogift57.xyz", + "csgogift58.xyz", + "csgogift59.xyz", + "csgogift60.xyz", + "csgogift62.xyz", + "csgogift77.xyz", + "csgogpusk.ru", + "csgoindex.ru.com", + "csgoindex.ru", + "csgoitemdetails.com", + "csgoitemsprices.com", + "csgojs.xyz", + "csgojump.ru", + "csgoko.tk", + "csgold.monster", + "csgomarble.xyz", + "csgomarketplace.net", + "csgomarkets.net", + "csgonavi.com", + "csgoorun.ru", + "csgoprocupgo.com", + "csgorcup.com", + "csgoroll.ru", + "csgorose.com", + "csgoroulette.monster", + "csgoroyalskins1.com", + "csgorun-rubonus.ru", + "csgorun.info", + "csgorun.pro-login.ru", + "csgorun.pro-loginn.com", + "csgosell.xyz", + "csgoskill.ru", + "csgoskinprices.com", + "csgoskinsinfo.com", + "csgoskinsroll.com", + "csgosprod.com", + "csgossteam.ru", + "csgossteam.xyz", + "csgostats.fun", + "csgosteam-game.ru", + "csgosteam-play.ru", + "csgosteamanalysis.com", + "csgosteamanalyst.ru", + "csgosteamcom.ru", + "csgosteamgo.ru", + "csgoteammate.gq", + "csgothunby.com", + "csgotournaments.cf", + "csgotrades.net", + "csgotreder.com", + "csgovip.ru", + "csgowans.ru", + "csgowaycup.ru.com", + "csgowincase.xyz", + "csgoworkshops.com", + "csgoxgiveaway.ru", + "csgozone.net.in", + "csgunskins.xyz", + "cslpkmf.ru", + "csm-oney.ru", + "csmarkete.info", + "csmone-y.ru", + "csmoneyskinz.xyz", + "csmvcecup.com", + "csogamech.xyz", + "csogamecm.xyz", + "csogamee.xyz", + "csogamef.xyz", + "csogamegg.ru", + "csogameke.xyz", + "csoggskif.ru", + "csoggskif.xyz", + "csogzhnc.xyz", + "csprices.in", + "csrandom.monster", + "css500gggo.ru", + "csskill.com", + "csskillpro.xyz", + "csskins.space", + "csskinz.xyz", + "csteamskin.ru", + "cstournament.ru", + "cswanmei.ru", + "cswanmei4.ru", + "cswinterpresent.xyz", + "csxrnoney.com", + "cteamcamnynity67823535672.xyz", + "cteamcommunity.xyz", + "cubesmc.ru", + "cupcs.ru", + "cupcsgo.ru", + "cupgoo.xyz", + "cupsul.ru", + "cupwin.xyz", + "cyber-csgo.link", + "cyber-csgo.space", + "cyber-lan.com", + "cyber-roll.club", + "cyber-roll.monster", + "cyber-shok.online", + "cyber-shok.ru", + "cyber-win.ru", + "cyber-x.xyz", + "cybercsgo.link", + "cyberdex.ru", + "cyberegocscom.ru", + "cyberesports-tournaments.ru", + "cybergamearena.ru", + "cyberiaevents.ru", + "cyberlev.ru", + "cybermode.ru", + "cyberscsgo.ru", + "cyberspark.org.ru", + "d-nitro.tk", + "d.iscord.xyz", + "d.myticks.xyz", + "d1scord.xyz", + "d1scrod.site", + "d2csbox.pp.ua", + "d2cups.com", + "d2faceit.com", + "d3l3.tk", + "dac-game.xyz", + "daddsda.xyz", + "dailymegadeal.xyz", + "dawbab.xyz", + "daxrop.xyz", + "dciscord.com", + "ddiscord.com", + "deadisidddde.xyz", + "deamonbets.ru", + "def-dclss.pp.ua", + "demonbets.ru", + "denforapasi.cf", + "der-csgo.ru", + "derimonz.xyz", + "derwoood.xyz", + "desmond.ru.com", + "determined-haslett.45-138-72-103.plesk.page", + "dfiscord.com", + "diablobets.com", + "diacordapp.com", + "diascord.com", + "diccrd.com", + "dicksod.co", + "dicoapp.me", + "dicoapp.pro", + "dicord.gg", + "dicord.gift", + "dicord.site", + "dicord.space", + "dicordapp.com", + "dicordgift.ru.com", + "dicordglfts.ga", + "dicordglfts.gq", + "dicovrd.com", + "dicrod.com", + "dicscordapp.com", + "dicsocrd.com", + "dicsord-airdrop.com", + "dicsord-airdrop.ru", + "dicsord-app.com", + "dicsord-events.com", + "dicsord-gift.com", + "dicsord-gifte.ru.com", + "dicsord-gifted.ru", + "dicsord-gifts.ru", + "dicsord-give.com", + "dicsord-give.ru", + "dicsord-gives.com", + "dicsord-hypesquads.com", + "dicsord-nitro.com", + "dicsord-nitro.ru", + "dicsord-steam.com", + "dicsord-ticket.com", + "dicsord.gg", + "dicsord.gifts", + "dicsord.net", + "dicsord.pl", + "dicsord.pw", + "dicsord.ru", + "dicsord.space", + "dicsord.website", + "dicsordapp.co", + "dicsordgift.club", + "dicsordgift.com", + "dicsordgive.ru.com", + "dicsordnitro.info", + "dicsordnitro.store", + "dicsordr.xyz", + "dicsords-gift.ru", + "dicsords.ru", + "dicsrod.com", + "didiscord.com", + "didscord.com", + "diiiscrod.club", + "diisccord.club", + "diiscord-app.com", + "diiscord-gift.com", + "diiscord-nittro.ru", + "diiscord.com", + "dIiscord.com", + "diiscord.gift", + "diiscord.me", + "diiscordapp.com", + "diisscord.club", + "diisscord.online", + "dijscord.com", + "dilscord.com", + "dioscord.com", + "diqscordapp.com", + "dircode.ru", + "direct-link.net", + "dirolzz.xyz", + "dirscod.com", + "dirscod.gift", + "dirscord-gift.ru", + "dirscordapp.com", + "dis.cord.gifts", + "disbordapp.com", + "disbords.com", + "disbored.com", + "disc-ord.com", + "disc.cool", + "disc.gifts", + "disc0rd-app.ru.com", + "disc0rd-nitro.site", + "disc0rd.org", + "disc0rd.site", + "disc0rd.xyz", + "discapp.info", + "discard.gg", + "