aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/arguments/abbreviatedNumber.ts13
-rw-r--r--src/arguments/contentWithDuration.ts5
-rw-r--r--src/arguments/discordEmoji.ts14
-rw-r--r--src/arguments/duration.ts5
-rw-r--r--src/arguments/durationSeconds.ts6
-rw-r--r--src/arguments/globalUser.ts7
-rw-r--r--src/arguments/index.ts10
-rw-r--r--src/arguments/messageLink.ts20
-rw-r--r--src/arguments/permission.ts12
-rw-r--r--src/arguments/roleWithDuration.ts17
-rw-r--r--src/arguments/snowflake.ts8
-rw-r--r--src/arguments/tinyColor.ts10
-rw-r--r--src/bot.ts6
-rw-r--r--src/commands/admin/channelPermissions.ts2
-rw-r--r--src/commands/dev/test.ts6
-rw-r--r--src/commands/info/help.ts2
-rw-r--r--src/commands/moderation/massEvidence.ts2
-rw-r--r--src/commands/moderation/myLogs.ts2
-rw-r--r--src/commands/moderation/unmute.ts2
-rw-r--r--src/commands/moulberry-bush/neuRepo.ts2
-rw-r--r--src/commands/moulberry-bush/rule.ts2
-rw-r--r--src/commands/utilities/calculator.ts2
-rw-r--r--src/commands/utilities/highlight-block.ts2
-rw-r--r--src/commands/utilities/highlight-unblock.ts2
-rw-r--r--src/commands/utilities/uuid.ts2
-rw-r--r--src/commands/utilities/wolframAlpha.ts2
-rw-r--r--src/context-menu-commands/message/viewRaw.ts2
-rw-r--r--src/context-menu-commands/user/modlog.ts2
-rw-r--r--src/context-menu-commands/user/userInfo.ts2
-rw-r--r--src/lib/badlinks.ts6930
-rw-r--r--src/lib/badwords.ts752
-rw-r--r--src/lib/common/AutoMod.ts529
-rw-r--r--src/lib/common/ButtonPaginator.ts219
-rw-r--r--src/lib/common/ConfirmationPrompt.ts64
-rw-r--r--src/lib/common/DeleteButton.ts78
-rw-r--r--src/lib/common/HighlightManager.ts485
-rw-r--r--src/lib/common/Sentry.ts24
-rw-r--r--src/lib/common/tags.ts34
-rw-r--r--src/lib/common/typings/BushInspectOptions.ts123
-rw-r--r--src/lib/common/typings/CodeBlockLang.ts311
-rw-r--r--src/lib/common/util/Arg.ts192
-rw-r--r--src/lib/common/util/Format.ts119
-rw-r--r--src/lib/common/util/Minecraft.ts349
-rw-r--r--src/lib/common/util/Minecraft_Test.ts86
-rw-r--r--src/lib/common/util/Moderation.ts556
-rw-r--r--src/lib/extensions/discord-akairo/BushArgumentTypeCaster.ts3
-rw-r--r--src/lib/extensions/discord-akairo/BushClient.ts586
-rw-r--r--src/lib/extensions/discord-akairo/BushCommand.ts586
-rw-r--r--src/lib/extensions/discord-akairo/BushCommandHandler.ts37
-rw-r--r--src/lib/extensions/discord-akairo/BushInhibitor.ts19
-rw-r--r--src/lib/extensions/discord-akairo/BushInhibitorHandler.ts3
-rw-r--r--src/lib/extensions/discord-akairo/BushListener.ts3
-rw-r--r--src/lib/extensions/discord-akairo/BushListenerHandler.ts3
-rw-r--r--src/lib/extensions/discord-akairo/BushTask.ts3
-rw-r--r--src/lib/extensions/discord-akairo/BushTaskHandler.ts3
-rw-r--r--src/lib/extensions/discord-akairo/SlashMessage.ts3
-rw-r--r--src/lib/extensions/discord.js/BushClientEvents.ts200
-rw-r--r--src/lib/extensions/discord.js/ExtendedGuild.ts916
-rw-r--r--src/lib/extensions/discord.js/ExtendedGuildMember.ts1255
-rw-r--r--src/lib/extensions/discord.js/ExtendedMessage.ts12
-rw-r--r--src/lib/extensions/discord.js/ExtendedUser.ts35
-rw-r--r--src/lib/extensions/global.ts13
-rw-r--r--src/lib/index.ts53
-rw-r--r--src/lib/models/BaseModel.ts13
-rw-r--r--src/lib/models/instance/ActivePunishment.ts94
-rw-r--r--src/lib/models/instance/Guild.ts422
-rw-r--r--src/lib/models/instance/Highlight.ts81
-rw-r--r--src/lib/models/instance/Level.ts70
-rw-r--r--src/lib/models/instance/ModLog.ts127
-rw-r--r--src/lib/models/instance/Reminder.ts84
-rw-r--r--src/lib/models/instance/StickyRole.ts58
-rw-r--r--src/lib/models/shared/Global.ts67
-rw-r--r--src/lib/models/shared/GuildCount.ts39
-rw-r--r--src/lib/models/shared/MemberCount.ts38
-rw-r--r--src/lib/models/shared/Shared.ts84
-rw-r--r--src/lib/models/shared/Stat.ts72
-rw-r--r--src/lib/utils/AllowedMentions.ts68
-rw-r--r--src/lib/utils/BushCache.ts26
-rw-r--r--src/lib/utils/BushClientUtils.ts498
-rw-r--r--src/lib/utils/BushConstants.ts531
-rw-r--r--src/lib/utils/BushLogger.ts315
-rw-r--r--src/lib/utils/BushUtils.ts612
-rw-r--r--src/lib/utils/CanvasProgressBar.ts83
-rw-r--r--src/listeners/automod/automodCreate.ts (renamed from src/listeners/message/automodCreate.ts)5
-rw-r--r--src/listeners/automod/automodUpdate.ts (renamed from src/listeners/message/automodUpdate.ts)6
-rw-r--r--src/listeners/automod/memberAutomod.ts21
-rw-r--r--src/listeners/automod/presenceAutomod.ts27
-rw-r--r--src/listeners/commands/commandError.ts4
-rw-r--r--src/listeners/interaction/interactionCreate.ts4
-rw-r--r--src/listeners/member-custom/bushLevelUpdate.ts6
-rw-r--r--src/tasks/cache/updateCache.ts4
-rw-r--r--src/tasks/cache/updateHighlightCache.ts4
-rw-r--r--src/tasks/cache/updatePriceItemCache.ts7
-rw-r--r--src/tasks/feature/handleReminders.ts3
-rw-r--r--src/tasks/stats/guildCount.ts2
-rw-r--r--src/tsconfig.json9
96 files changed, 106 insertions, 18131 deletions
diff --git a/src/arguments/abbreviatedNumber.ts b/src/arguments/abbreviatedNumber.ts
deleted file mode 100644
index a7d8ce5..0000000
--- a/src/arguments/abbreviatedNumber.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { BushArgumentTypeCaster } from '#lib';
-import assert from 'assert/strict';
-import numeral from 'numeral';
-assert(typeof numeral === 'function');
-
-export const abbreviatedNumber: BushArgumentTypeCaster<number | null> = (_, phrase) => {
- if (!phrase) return null;
- const num = numeral(phrase?.toLowerCase()).value();
-
- if (typeof num !== 'number' || isNaN(num)) return null;
-
- return num;
-};
diff --git a/src/arguments/contentWithDuration.ts b/src/arguments/contentWithDuration.ts
deleted file mode 100644
index 0efba39..0000000
--- a/src/arguments/contentWithDuration.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { parseDuration, type BushArgumentTypeCaster, type ParsedDuration } from '#lib';
-
-export const contentWithDuration: BushArgumentTypeCaster<Promise<ParsedDuration>> = async (_, phrase) => {
- return parseDuration(phrase);
-};
diff --git a/src/arguments/discordEmoji.ts b/src/arguments/discordEmoji.ts
deleted file mode 100644
index 92d6502..0000000
--- a/src/arguments/discordEmoji.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { regex, type BushArgumentTypeCaster } from '#lib';
-import type { Snowflake } from 'discord.js';
-
-export const discordEmoji: BushArgumentTypeCaster<DiscordEmojiInfo | null> = (_, 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/src/arguments/duration.ts b/src/arguments/duration.ts
deleted file mode 100644
index 09dd3d5..0000000
--- a/src/arguments/duration.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { parseDuration, type BushArgumentTypeCaster } from '#lib';
-
-export const duration: BushArgumentTypeCaster<number | null> = (_, phrase) => {
- return parseDuration(phrase).duration;
-};
diff --git a/src/arguments/durationSeconds.ts b/src/arguments/durationSeconds.ts
deleted file mode 100644
index d8d6749..0000000
--- a/src/arguments/durationSeconds.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { parseDuration, type BushArgumentTypeCaster } from '#lib';
-
-export const durationSeconds: BushArgumentTypeCaster<number | null> = (_, phrase) => {
- phrase += 's';
- return parseDuration(phrase).duration;
-};
diff --git a/src/arguments/globalUser.ts b/src/arguments/globalUser.ts
deleted file mode 100644
index 4324aa9..0000000
--- a/src/arguments/globalUser.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import type { BushArgumentTypeCaster } from '#lib';
-import type { User } from 'discord.js';
-
-// resolve non-cached users
-export const globalUser: BushArgumentTypeCaster<Promise<User | null>> = async (message, phrase) => {
- return message.client.users.resolve(phrase) ?? (await message.client.users.fetch(`${phrase}`).catch(() => null));
-};
diff --git a/src/arguments/index.ts b/src/arguments/index.ts
deleted file mode 100644
index eebf0a2..0000000
--- a/src/arguments/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-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/src/arguments/messageLink.ts b/src/arguments/messageLink.ts
deleted file mode 100644
index c95e42d..0000000
--- a/src/arguments/messageLink.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { BushArgumentTypeCaster, regex } from '#lib';
-import type { Message } from 'discord.js';
-
-export const messageLink: BushArgumentTypeCaster<Promise<Message | null>> = 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/src/arguments/permission.ts b/src/arguments/permission.ts
deleted file mode 100644
index 98bfe74..0000000
--- a/src/arguments/permission.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import type { BushArgumentTypeCaster } from '#lib';
-import { PermissionFlagsBits, type PermissionsString } from 'discord.js';
-
-export const permission: BushArgumentTypeCaster<PermissionsString | null> = (_, phrase) => {
- if (!phrase) return null;
- phrase = phrase.toUpperCase().replace(/ /g, '_');
- if (!(phrase in PermissionFlagsBits)) {
- return null;
- } else {
- return phrase as PermissionsString;
- }
-};
diff --git a/src/arguments/roleWithDuration.ts b/src/arguments/roleWithDuration.ts
deleted file mode 100644
index b97f205..0000000
--- a/src/arguments/roleWithDuration.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Arg, BushArgumentTypeCaster, parseDuration } from '#lib';
-import type { Role } from 'discord.js';
-
-export const roleWithDuration: BushArgumentTypeCaster<Promise<RoleWithDuration | null>> = 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/src/arguments/snowflake.ts b/src/arguments/snowflake.ts
deleted file mode 100644
index b98a20f..0000000
--- a/src/arguments/snowflake.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { BushArgumentTypeCaster, regex } from '#lib';
-import type { Snowflake } from 'discord.js';
-
-export const snowflake: BushArgumentTypeCaster<Snowflake | null> = (_, phrase) => {
- if (!phrase) return null;
- if (regex.snowflake.test(phrase)) return phrase;
- return null;
-};
diff --git a/src/arguments/tinyColor.ts b/src/arguments/tinyColor.ts
deleted file mode 100644
index 148c078..0000000
--- a/src/arguments/tinyColor.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import type { BushArgumentTypeCaster } from '#lib';
-import assert from 'assert/strict';
-import tinycolorModule from 'tinycolor2';
-assert(tinycolorModule);
-
-export const tinyColor: BushArgumentTypeCaster<string | null> = (_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/src/bot.ts b/src/bot.ts
index 038fbbb..10818e9 100644
--- a/src/bot.ts
+++ b/src/bot.ts
@@ -1,4 +1,4 @@
-import { init } from './lib/utils/BushLogger.js';
+import { init } from '../lib/utils/BushLogger.js';
// creates proxies on console.log and console.warn
// also starts a REPL session
init();
@@ -6,8 +6,8 @@ init();
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/index.js';
+import { Sentry } from '../lib/common/Sentry.js';
+import { BushClient } from '../lib/extensions/discord-akairo/BushClient.js';
const isDry = process.argv.includes('dry');
if (!isDry && config.credentials.sentryDsn !== null) new Sentry(dirname(fileURLToPath(import.meta.url)) || process.cwd(), config);
diff --git a/src/commands/admin/channelPermissions.ts b/src/commands/admin/channelPermissions.ts
index 0b09e54..21abd04 100644
--- a/src/commands/admin/channelPermissions.ts
+++ b/src/commands/admin/channelPermissions.ts
@@ -1,6 +1,5 @@
import {
Arg,
- BushCommand,
ButtonPaginator,
clientSendAndPermCheck,
emojis,
@@ -11,6 +10,7 @@ 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/dev/test.ts b/src/commands/dev/test.ts
index ac0ad83..0606497 100644
--- a/src/commands/dev/test.ts
+++ b/src/commands/dev/test.ts
@@ -18,9 +18,9 @@ import {
type ApplicationCommand,
type Collection
} from 'discord.js';
-import badLinksSecretArray from '../../lib/badlinks-secret.js';
-import badLinksArray from '../../lib/badlinks.js';
-import badWords from '../../lib/badwords.js';
+import badLinksSecretArray from '../../../lib/badlinks-secret.js';
+import badLinksArray from '../../../lib/badlinks.js';
+import badWords from '../../../lib/badwords.js';
export default class TestCommand extends BushCommand {
public constructor() {
diff --git a/src/commands/info/help.ts b/src/commands/info/help.ts
index 348c74f..62f177e 100644
--- a/src/commands/info/help.ts
+++ b/src/commands/info/help.ts
@@ -9,6 +9,7 @@ import {
type OptArgType,
type SlashMessage
} from '#lib';
+import { stripIndent } from '#tags';
import assert from 'assert/strict';
import {
ActionRowBuilder,
@@ -21,7 +22,6 @@ import {
} from 'discord.js';
import Fuse from 'fuse.js';
import packageDotJSON from '../../../package.json' assert { type: 'json' };
-import { stripIndent } from '../../lib/common/tags.js';
assert(Fuse);
assert(packageDotJSON);
diff --git a/src/commands/moderation/massEvidence.ts b/src/commands/moderation/massEvidence.ts
index 62f4825..cecf273 100644
--- a/src/commands/moderation/massEvidence.ts
+++ b/src/commands/moderation/massEvidence.ts
@@ -13,7 +13,7 @@ import {
} from '#lib';
import assert from 'assert/strict';
import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js';
-import { EvidenceCommand } from '../index.js';
+import EvidenceCommand from './evidence.js';
export default class MassEvidenceCommand extends BushCommand {
public constructor() {
diff --git a/src/commands/moderation/myLogs.ts b/src/commands/moderation/myLogs.ts
index ab67a18..c1cc448 100644
--- a/src/commands/moderation/myLogs.ts
+++ b/src/commands/moderation/myLogs.ts
@@ -12,7 +12,7 @@ import {
} from '#lib';
import { ApplicationCommandOptionType } from 'discord.js';
-import { input, sanitizeInputForDiscord } from '../../lib/common/util/Format.js';
+import { input, sanitizeInputForDiscord } from '../../../lib/utils/Format.js';
import ModlogCommand from './modlog.js';
export default class MyLogsCommand extends BushCommand {
public constructor() {
diff --git a/src/commands/moderation/unmute.ts b/src/commands/moderation/unmute.ts
index 648a178..620f499 100644
--- a/src/commands/moderation/unmute.ts
+++ b/src/commands/moderation/unmute.ts
@@ -14,7 +14,7 @@ import {
} from '#lib';
import assert from 'assert/strict';
import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js';
-import { BushCommand } from '../../lib/extensions/discord-akairo/BushCommand.js';
+import { BushCommand } from '../../../lib/extensions/discord-akairo/BushCommand.js';
export default class UnmuteCommand extends BushCommand {
public constructor() {
diff --git a/src/commands/moulberry-bush/neuRepo.ts b/src/commands/moulberry-bush/neuRepo.ts
index d07ba53..fcb6f23 100644
--- a/src/commands/moulberry-bush/neuRepo.ts
+++ b/src/commands/moulberry-bush/neuRepo.ts
@@ -10,7 +10,7 @@ import {
import { dirname, join } from 'path';
import tinycolor from 'tinycolor2';
import { fileURLToPath } from 'url';
-import { formattingInfo, RawNeuItem } from '../../lib/common/util/Minecraft.js';
+import { formattingInfo, RawNeuItem } from '../../../lib/utils/Minecraft.js';
export default class NeuRepoCommand extends BushCommand {
public static items: { name: string; id: string }[] = [];
diff --git a/src/commands/moulberry-bush/rule.ts b/src/commands/moulberry-bush/rule.ts
index 25a3ef0..ab5500d 100644
--- a/src/commands/moulberry-bush/rule.ts
+++ b/src/commands/moulberry-bush/rule.ts
@@ -8,8 +8,8 @@ import {
type OptArgType,
type SlashMessage
} from '#lib';
+import { stripIndent } from '#tags';
import { ApplicationCommandOptionType, EmbedBuilder, PermissionFlagsBits } from 'discord.js';
-import { stripIndent } from '../../lib/common/tags.js';
const rules = [
{
diff --git a/src/commands/utilities/calculator.ts b/src/commands/utilities/calculator.ts
index c9dbbf2..dc5593b 100644
--- a/src/commands/utilities/calculator.ts
+++ b/src/commands/utilities/calculator.ts
@@ -52,7 +52,7 @@ export default class CalculatorCommand extends BushCommand {
name: '📤 Output',
value: await this.client.utils.inspectCleanRedactCodeblock(calculated.toString(), 'mma')
});
- } catch (error) {
+ } catch (error: any) {
decodedEmbed
.setTitle(`${emojis.errorFull} Unable to Calculate Expression`)
.setColor(colors.error)
diff --git a/src/commands/utilities/highlight-block.ts b/src/commands/utilities/highlight-block.ts
index a450d71..58e7766 100644
--- a/src/commands/utilities/highlight-block.ts
+++ b/src/commands/utilities/highlight-block.ts
@@ -2,7 +2,7 @@ import { AllowedMentions, BushCommand, emojis, type ArgType, type CommandMessage
import assert from 'assert/strict';
import { Argument, ArgumentGeneratorReturn } from 'discord-akairo';
import { BaseChannel, GuildMember, User } from 'discord.js';
-import { HighlightBlockResult } from '../../lib/common/HighlightManager.js';
+import { HighlightBlockResult } from '../../../lib/common/HighlightManager.js';
import { highlightSubcommands } from './highlight-!.js';
export default class HighlightBlockCommand extends BushCommand {
diff --git a/src/commands/utilities/highlight-unblock.ts b/src/commands/utilities/highlight-unblock.ts
index 702fa65..2238831 100644
--- a/src/commands/utilities/highlight-unblock.ts
+++ b/src/commands/utilities/highlight-unblock.ts
@@ -2,7 +2,7 @@ import { AllowedMentions, BushCommand, emojis, type ArgType, type CommandMessage
import assert from 'assert';
import { Argument, ArgumentGeneratorReturn } from 'discord-akairo';
import { BaseChannel, GuildMember, User } from 'discord.js';
-import { HighlightUnblockResult } from '../../lib/common/HighlightManager.js';
+import { HighlightUnblockResult } from '../../../lib/common/HighlightManager.js';
import { highlightSubcommands } from './highlight-!.js';
export default class HighlightUnblockCommand extends BushCommand {
diff --git a/src/commands/utilities/uuid.ts b/src/commands/utilities/uuid.ts
index 3f99e66..04d4013 100644
--- a/src/commands/utilities/uuid.ts
+++ b/src/commands/utilities/uuid.ts
@@ -23,7 +23,7 @@ export default class UuidCommand extends BushCommand {
{
id: 'ign',
description: 'The ign to find the ign of.',
- customType: /\w{1,16}/im,
+ customType: /^\w{1,16}$/im,
readableType: 'string[1,16]',
prompt: 'What ign would you like to find the uuid of?',
retry: '{error} Choose a valid ign.',
diff --git a/src/commands/utilities/wolframAlpha.ts b/src/commands/utilities/wolframAlpha.ts
index b35e14f..5ba55f7 100644
--- a/src/commands/utilities/wolframAlpha.ts
+++ b/src/commands/utilities/wolframAlpha.ts
@@ -87,6 +87,8 @@ export default class WolframAlphaCommand extends BushCommand {
});
}
} catch (error) {
+ assert(error instanceof Error);
+
decodedEmbed
.setTitle(`${emojis.errorFull} Unable to Query Expression`)
.setColor(colors.error)
diff --git a/src/context-menu-commands/message/viewRaw.ts b/src/context-menu-commands/message/viewRaw.ts
index 6cfe552..f216a59 100644
--- a/src/context-menu-commands/message/viewRaw.ts
+++ b/src/context-menu-commands/message/viewRaw.ts
@@ -1,6 +1,6 @@
-import { ViewRawCommand } from '#commands';
import { ContextMenuCommand } from 'discord-akairo';
import { ApplicationCommandType, type ContextMenuCommandInteraction, type Message } from 'discord.js';
+import ViewRawCommand from '../../commands/utilities/viewRaw.js';
export default class ViewRawContextMenuCommand extends ContextMenuCommand {
public constructor() {
diff --git a/src/context-menu-commands/user/modlog.ts b/src/context-menu-commands/user/modlog.ts
index c78396e..91b1b62 100644
--- a/src/context-menu-commands/user/modlog.ts
+++ b/src/context-menu-commands/user/modlog.ts
@@ -1,7 +1,7 @@
-import { ModlogCommand } from '#commands';
import { emojis, SlashMessage } from '#lib';
import { CommandUtil, ContextMenuCommand } from 'discord-akairo';
import { ApplicationCommandType, type ContextMenuCommandInteraction } from 'discord.js';
+import ModlogCommand from '../../commands/moderation/modlog.js';
export default class ModlogContextMenuCommand extends ContextMenuCommand {
public constructor() {
diff --git a/src/context-menu-commands/user/userInfo.ts b/src/context-menu-commands/user/userInfo.ts
index 6d7f3b6..0d19cce 100644
--- a/src/context-menu-commands/user/userInfo.ts
+++ b/src/context-menu-commands/user/userInfo.ts
@@ -1,7 +1,7 @@
-import { UserInfoCommand } from '#commands';
import { format } from '#lib';
import { ContextMenuCommand } from 'discord-akairo';
import { ApplicationCommandType, type ContextMenuCommandInteraction, type Guild } from 'discord.js';
+import UserInfoCommand from '../../commands/info/userInfo.js';
export default class UserInfoContextMenuCommand extends ContextMenuCommand {
public constructor() {
diff --git a/src/lib/badlinks.ts b/src/lib/badlinks.ts
deleted file mode 100644
index 3b4cf3b..0000000
--- a/src/lib/badlinks.ts
+++ /dev/null
@@ -1,6930 +0,0 @@
-/* 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",
- "discard.gift",
- "discard.xyz",
- "discardapp.fun",
- "disccor.com",
- "disccord-apps.com",
- "disccord-appss.ru",
- "disccord-club.com",
- "disccord-gift.com",
- "disccord.gg",
- "disccord.ru.com",
- "disccord.ru",
- "disccord.shop",
- "disccord.tk",
- "disccords.com",
- "disccrd.gifts",
- "disccrdapp.com",
- "disceord.gift",
- "discerd.gift",
- "discford.com",
- "discgrdapp.com",
- "dischrd.com",
- "discird.gg",
- "discird.me",
- "discjrd.com",
- "disckord.com",
- "disckordapp.com",
- "disclord.com",
- "disclrd.com",
- "discnrd.gift",
- "discnrdapp.com",
- "disco.to",
- "disco3d.app",
- "disco9rdapp.com",
- "discoapps.club",
- "discoard.com",
- "discocd.com",
- "discocdapp.com",
- "discocl.xyz",
- "discoclapp.xyz",
- "discocord.com",
- "discocrd-gift.com",
- "discocrd-gifts.com",
- "discocrd-nitro.com",
- "discocrd.gift",
- "discocrd.gifts",
- "discocrdapp.com",
- "discod-hitro.xyz",
- "discod-nitro.ru",
- "discod.art",
- "discod.fun",
- "discod.gift",
- "discod.gifts",
- "discod.info",
- "discod.tech",
- "discodapp.gift",
- "discodapp.net",
- "discode.gift",
- "discodnitro.info",
- "discodnitro.ru",
- "discodrd.com",
- "discoed.gg",
- "discoed.me",
- "discoerd.com",
- "discoerdapp.com",
- "discofd.com",
- "discokrd.com",
- "discold.online",
- "discold.ru",
- "discolrd.com",
- "discond-nitro.ru",
- "discond-njtro.tech",
- "discond.gift",
- "discond.ru.com",
- "discondapp.fun",
- "disconrd.com",
- "discontro.ru",
- "discoogs.com",
- "discoord-apps.com",
- "discoord-nitro.com",
- "discoord.space",
- "discor-dnitro.fun",
- "discor.de",
- "discor.gg",
- "discor.link",
- "discor.me",
- "discorad.com",
- "discorapp.gq",
- "discorapp.pw",
- "discorb-nitro.ru.com",
- "discorb.blog",
- "discorb.co",
- "discorb.com",
- "discorb.gift",
- "discorb.gifts",
- "discorb.ru.com",
- "discorc-nitro.site",
- "discorcd-apps.com",
- "discorcd-gift.com",
- "discorcd-nitro.com",
- "discorcd.click",
- "discorcd.com",
- "discorcd.gift",
- "discorcd.gifts",
- "discorcd.site",
- "discorcdapp.com",
- "discorci.com",
- "discorcl-air.xyz",
- "discorcl-app.com",
- "discorcl-app.ru",
- "discorcl-app.xyz",
- "discorcl-boost.ru",
- "discorcl-gift.org.ru",
- "discorcl-gift.ru.com",
- "discorcl-gift.ru",
- "discorcl-gift.xyz",
- "discorcl-give.site",
- "discorcl-nitro.com",
- "discorcl-nitro.ru.com",
- "discorcl-nitro.site",
- "discorcl.app",
- "discorcl.art",
- "discorcl.click",
- "discorcl.club",
- "discorcl.fun",
- "discorcl.ga",
- "discorcl.gift",
- "discorcl.gifts",
- "discorcl.info",
- "discorcl.link",
- "discorcl.online",
- "discorcl.ru.com",
- "discorcl.ru",
- "discorcl.shop",
- "discorcl.site",
- "discorcl.store",
- "discorclapp.com",
- "discorclapp.fun",
- "discorclgift.com",
- "discorclgift.xyz",
- "discorcll.com",
- "discorcll.online",
- "discorclnitro.ru",
- "discorclsteam.com",
- "discorcrd.gift",
- "discorcz-booster.ru",
- "discord-a.com",
- "discord-accept.com",
- "discord-accounts.com",
- "discord-accounts.ru",
- "discord-air.fun",
- "discord-air.pw",
- "discord-air.xyz",
- "discord-airclrop.pw",
- "discord-airdop.link",
- "discord-airdrop.com",
- "discord-airdrop.fun",
- "discord-airdrop.info",
- "discord-airdrop.me",
- "discord-airdrop.pw",
- "discord-airdrop.site",
- "discord-airdrop.xyz",
- "discord-airnitro.xyz",
- "discord-alidrop.me",
- "discord-alrdrop.com",
- "discord-app.cc",
- "discord-app.click",
- "discord-app.club",
- "discord-app.co.uk",
- "discord-app.co",
- "discord-app.gift",
- "discord-app.gifts",
- "discord-app.info",
- "discord-app.io",
- "discord-app.live",
- "discord-app.me",
- "discord-app.net",
- "discord-app.ru.com",
- "discord-app.shop",
- "discord-app.store",
- "discord-app.su",
- "discord-app.top",
- "discord-app.uk",
- "discord-app.us",
- "discord-app.xyz",
- "discord-application.com",
- "discord-applications.com",
- "discord-apply.com",
- "discord-appnitro.com",
- "discord-apps.ru",
- "discord-apps.site",
- "discord-apps.space",
- "discord-apps.xyz",
- "discord-best-nitro.xyz",
- "discord-bonus.ru",
- "discord-boost.com",
- "discord-boost.ru.com",
- "discord-boost.ru",
- "discord-boost.xyz",
- "discord-bot.com",
- "discord-bot.ru",
- "discord-bugs.com",
- "discord-claim.com",
- "discord-claim.ru.com",
- "discord-claim.ru",
- "discord-clap.com",
- "discord-click.shop",
- "discord-club.ru",
- "discord-com-free.online",
- "discord-com-free.ru",
- "discord-control.com",
- "discord-controls.com",
- "discord-cpp.com",
- "discord-develop.com",
- "discord-developer.com",
- "discord-devs.com",
- "discord-do.com",
- "discord-dr0p.ru",
- "discord-drop.gift",
- "discord-drop.info",
- "discord-drop.xyz",
- "discord-drops.ru",
- "discord-egift.com",
- "discord-event.com",
- "discord-event.info",
- "discord-events.com",
- "discord-exploits.tk",
- "discord-faq.com",
- "discord-free-nitro.ru",
- "discord-free.com",
- "discord-free.site",
- "discord-freenitro.online",
- "discord-freenitro.pw",
- "discord-fun.com",
- "discord-game.com",
- "discord-games.cf",
- "discord-generator.tk",
- "discord-get.click",
- "discord-get.ru",
- "discord-gg.com",
- "discord-gg.ru.com",
- "discord-gif.xyz",
- "discord-gifft.com",
- "discord-gift-free-nitro.tk",
- "discord-gift-nitro.site",
- "discord-gift.app",
- "discord-gift.info",
- "discord-gift.net.ru",
- "discord-gift.online",
- "discord-gift.ru.com",
- "discord-gift.ru",
- "discord-gift.shop",
- "discord-gift.site",
- "discord-gift.top",
- "discord-gift.us",
- "discord-gifte.com",
- "discord-gifte.ru",
- "discord-gifte.xyz",
- "discord-gifted.ru.com",
- "discord-giftef.xyz",
- "discord-gifteh.xyz",
- "discord-giftes.com",
- "discord-gifts.com.ru",
- "discord-gifts.com",
- "discord-gifts.me",
- "discord-gifts.org",
- "discord-gifts.ru.com",
- "discord-gifts.shop",
- "discord-gifts.site",
- "discord-givaewey.ru",
- "discord-give.com",
- "discord-give.net",
- "discord-give.org",
- "discord-give.pw",
- "discord-give.ru.com",
- "discord-give.ru",
- "discord-give.xyz",
- "discord-giveaway.com",
- "discord-giveaways.ru",
- "discord-glft.com",
- "discord-glft.ru.com",
- "discord-glft.xyz",
- "discord-halloween-nitro.com",
- "discord-halloween.com",
- "discord-halloween.link",
- "discord-halloween.me",
- "discord-halloween.ru.com",
- "discord-halloween.ru",
- "discord-hallowen.ru.com",
- "discord-help.com",
- "discord-helpers.com",
- "discord-hse.com",
- "discord-hype.com",
- "discord-hypeevent.com",
- "discord-hypes.com",
- "discord-hypesquad.com",
- "discord-hypesquad.info",
- "discord-hypesquade.com",
- "discord-hypesquaders.com",
- "discord-hypesquads.com",
- "discord-hypevent.com",
- "discord-i.com",
- "discord-info.com",
- "discord-infoapp.xyz",
- "discord-information.com",
- "discord-information.ru",
- "discord-informations.com",
- "discord-informations.ru",
- "discord-install.com",
- "discord-invite-link.com",
- "discord-job.com",
- "discord-jobs.com",
- "discord-list.cf",
- "discord-load.ru",
- "discord-login.cf",
- "discord-mega.xyz",
- "discord-mod.com",
- "discord-moderation.com",
- "discord-moderator.com",
- "discord-moderator.us",
- "discord-mods.com",
- "discord-net-labs.com",
- "discord-netro.ru",
- "discord-news.com",
- "discord-niittro.ru",
- "discord-nilro.ru",
- "discord-niltro.com",
- "discord-niltro.ru.com",
- "discord-nitr0gift.fun",
- "discord-nitre.xyz",
- "discord-nitro-boost.xyz",
- "discord-nitro-classic.com",
- "discord-nitro-free.ml",
- "discord-nitro-free.ru",
- "discord-nitro-free.xyz",
- "discord-nitro.click",
- "discord-nitro.cloud",
- "discord-nitro.club",
- "discord-nitro.co",
- "discord-nitro.com",
- "discord-nitro.eu",
- "discord-nitro.gift",
- "discord-nitro.gifts",
- "discord-nitro.info",
- "discord-nitro.it",
- "discord-nitro.link",
- "discord-nitro.live",
- "discord-nitro.net",
- "discord-nitro.online",
- "discord-nitro.org",
- "discord-nitro.pro",
- "discord-nitro.ru.com",
- "discord-nitro.services",
- "discord-nitro.shop",
- "discord-nitro.store",
- "discord-nitro.su",
- "discord-nitro.tech",
- "discord-nitro.tk",
- "discord-nitro.website",
- "discord-nitroapp.ru",
- "discord-nitroapp.xyz",
- "discord-nitrodrop.xyz",
- "discord-nitroe.xyz",
- "discord-nitrogift.com",
- "discord-nitrogift.ru",
- "discord-nitrogift.xyz",
- "discord-nitros.com",
- "discord-nitros.ru",
- "discord-nitrot.xyz",
- "discord-njtro.store",
- "discord-nltro.com",
- "discord-nltro.fun",
- "discord-nltro.info",
- "discord-nltro.ru",
- "discord-nudes.club",
- "discord-nudes.live",
- "discord-o.com",
- "discord-offer.com",
- "discord-partner.com",
- "discord-partners.com",
- "discord-premium.com",
- "discord-present.ru",
- "discord-promo.com",
- "discord-promo.info",
- "discord-promo.ru.com",
- "discord-promo.site",
- "discord-promo.xyz",
- "discord-promotions.com",
- "discord-promox.com",
- "discord-report.com",
- "discord-ro.tk",
- "discord-ru.site",
- "discord-security.com",
- "discord-service.com",
- "discord-sex.live",
- "discord-shop.fun",
- "discord-sms.eu",
- "discord-soft.ru",
- "discord-spooky.ru",
- "discord-staff.com",
- "discord-stat.com",
- "discord-stats.com",
- "discord-stats.org",
- "discord-steam.com",
- "discord-steam.ru",
- "discord-steam.site",
- "discord-steams.com",
- "discord-stemdrop.me",
- "discord-stuff.com",
- "discord-sup.com",
- "discord-support.com",
- "discord-support.org",
- "discord-support.tech",
- "discord-supports.com",
- "discord-team.com",
- "discord-tech.com",
- "discord-tester.com",
- "discord-to.com",
- "discord-true.com",
- "discord-trustandsafety.com",
- "discord-up.ru",
- "discord-verif.ga",
- "discord-verification.com",
- "discord-verifications.com",
- "discord-verify-account.ml",
- "discord-verify.com",
- "discord-verify.ru",
- "discord-vetify.com",
- "discord-web.co",
- "discord-xnitro.com",
- "discord.1nitro.club",
- "discord.ac",
- "discord.app.br",
- "discord.app",
- "discord.bargains",
- "discord.best",
- "discord.biz",
- "discord.blog",
- "discord.cc",
- "discord.cloud",
- "discord.cm",
- "discord.cn.com",
- "discord.co.com",
- "discord.co.in",
- "discord.co.za",
- "discord.com.pl",
- "discord.com.tw",
- "discord.cool",
- "discord.creditcard",
- "discord.deals",
- "discord.download",
- "discord.es",
- "discord.eu",
- "discord.family",
- "discord.fit",
- "discord.foundation",
- "discord.fyi",
- "discord.gifte",
- "discord.givaeway.com",
- "discord.givaewey.com",
- "discord.giveawey.com",
- "discord.giveaweys.com",
- "discord.glfte.com",
- "discord.gq",
- "discord.homes",
- "discord.in",
- "discord.istanbul",
- "discord.limited",
- "discord.ltd",
- "discord.luxe",
- "discord.marketing",
- "discord.moscow",
- "discord.my",
- "dIscord.net",
- "discord.online",
- "discord.org.ru",
- "discord.porn",
- "discord.pp.ru",
- "discord.promo",
- "discord.pt",
- "discord.ru.net",
- "discord.shop",
- "discord.si",
- "discord.team",
- "discord.tools",
- "discord.tw",
- "discord.world",
- "discord2fa.com",
- "discord404.com",
- "discord4nitro.com",
- "discordaap.com",
- "discordacc2.repl.co",
- "discordadp.com",
- "discordadpp.com",
- "discordaepp.com",
- "discordalt4.repl.co",
- "discordalt5.repl.co",
- "discordalts293.repl.co",
- "discordaoo.com",
- "discordaop.com",
- "discordapp.best",
- "discordapp.biz",
- "discordapp.click",
- "discordapp.cloud",
- "discordapp.co.uk",
- "discordapp.eu",
- "discordapp.gg",
- "discordapp.help",
- "discordapp.ir",
- "discordapp.org",
- "discordapp.pages.dev",
- "discordapp.pw",
- "discordapp.rip",
- "discordapp.ru.com",
- "discordapp.social",
- "discordapp.store",
- "discordapp.support",
- "discordapp.top",
- "discordapp.us",
- "discordapp.vercel.app",
- "discordapp.vip",
- "discordapp.ws",
- "discordappi.fun",
- "discordapplication.com",
- "discordapplication.xyz",
- "discordapplications.com",
- "discordappo.com",
- "discordappp.com",
- "discordappp.net",
- "discordappporn.chat",
- "discordapps.gift",
- "discordapps.gifts",
- "discordapps.tk",
- "discordappss.com",
- "discordaspp.com",
- "discordbagequiz.cf",
- "discordbeta.com",
- "discordbetter.app",
- "discordboost.net",
- "discordbooster.com",
- "discordbothost.com",
- "discordbotist.com",
- "discordbots.app",
- "discordbugs.com",
- "discordc.gift",
- "discordcanary.com",
- "discordcdn.sa.com",
- "discordcharity.org",
- "discordcheats.net",
- "discordclgift.net.ru",
- "discordcommunlty.com",
- "discordcrasher.wtf",
- "discordcreators.net",
- "discordd.buzz",
- "discordd.gg",
- "discordd.gift",
- "discorddaapp.com",
- "discorddev.com",
- "discorddevelopment.com",
- "discorddevs.com",
- "discorddiscord.com",
- "discorddrop.com",
- "discorde-gift.com",
- "discorde-gifte.com",
- "discorde-nitro.com",
- "discorde.gift",
- "discorde.xyz",
- "discordevents.com",
- "discordf.com",
- "discordf.gift",
- "discordfree.com",
- "discordfrnitro.site",
- "discordg.com.ru",
- "discordg.link",
- "discordgame.com",
- "discordgamers.co.uk",
- "discordgft.com",
- "discordgg.com",
- "discordgif.com",
- "discordgift.app",
- "discordgift.com",
- "discordgift.fun",
- "discordgift.info",
- "discordgift.net.ru",
- "discordgift.org",
- "discordgift.pw",
- "discordgift.ru.com",
- "discordgift.ru",
- "discordgift.site",
- "discordgift.tk",
- "discordgift.xyz",
- "discordgifte.site",
- "discordgifted.xyz",
- "discordgiftis.ru",
- "discordgifts-pay.ru.com",
- "discordgifts-pay.ru",
- "discordgifts.co.uk",
- "discordgifts.com",
- "discordgifts.fun",
- "discordgifts.info",
- "discordgifts.link",
- "discordgifts.me",
- "discordgifts.ru.com",
- "discordgifts.ru",
- "discordgifts.site",
- "discordgifts.store",
- "discordgiftss.com",
- "discordgiftsteam.ru",
- "discordgiftz.xyz",
- "discordgive.ru.com",
- "discordgive.ru",
- "discordgiveaway.fun",
- "discordgivenitro.com",
- "discordgivenitro.ru.com",
- "discordglft.com",
- "discordglft.ru",
- "discordglfts.com",
- "discordglfts.xyz",
- "discordhalloween.co.uk",
- "discordhalloween.com",
- "discordhalloween.gift",
- "discordhalloween.uk",
- "discordi.gift",
- "discordiapp.fun",
- "discordiatech.co.uk",
- "discordicon.com",
- "discordimages.com",
- "discordinfo.com",
- "discordinfo.ru",
- "discordinvite.ml",
- "discordist.com",
- "discordj.gift",
- "discordjob.com",
- "discordjs.tech",
- "discordl-steam.com",
- "discordl.com",
- "discordl.pw",
- "discordl.site",
- "discordl.xyz",
- "discordlapp.fun",
- "discordlgift.com",
- "discordlgift.ru.com",
- "discordlinks.co.uk",
- "discordlist.repl.co",
- "discordlive.xyz",
- "discordll.gift",
- "discordlogin.com",
- "discordmac.com",
- "discordme.me",
- "discordmoderations.com",
- "discordn.com",
- "discordn.gift",
- "discordnitro-gift.com",
- "discordnitro-steam.ru",
- "discordnitro.altervista.org",
- "discordnitro.biz",
- "discordnitro.cc",
- "discordnitro.click",
- "discordnitro.club",
- "discordnitro.com",
- "dIscordnitro.com",
- "discordnitro.fun",
- "discordnitro.gift",
- "discordnitro.info",
- "discordnitro.link",
- "discordnitro.ru.com",
- "discordnitro.space",
- "discordnitro.store",
- "discordnitro.su",
- "discordnitro9.repl.co",
- "discordnitroapp.ru.com",
- "discordnitroevent.info",
- "discordnitrofree.com",
- "discordnitrofree.xyz",
- "discordnitrogenerator.com",
- "discordnitrogift.com",
- "discordnitrogift.ru",
- "discordnitrogifts.pl",
- "discordnitrolink.tk",
- "discordnitropromo.site",
- "discordnitros.gifts",
- "discordnitros.xyz",
- "discordnitrosteam.com",
- "discordnltro.com",
- "discordobs.com",
- "discordp.com",
- "discordp.ml",
- "discordpap.com",
- "discordpp.com",
- "discordprize.xyz",
- "discordpromo.site",
- "discordq.com",
- "discordqapp.com",
- "discordqpp.com",
- "discordqr.com",
- "discordre.store",
- "discordresearch.com",
- "discordrgift.com",
- "discordrgift.online",
- "discordrgift.ru",
- "discords-accounts.ru",
- "discords-app.com",
- "discords-dev.ga",
- "discords-developers.com",
- "discords-events.com",
- "discords-gift.com",
- "discords-gift.ru",
- "discords-gifte.ru",
- "discords-gifts.club",
- "discords-gifts.ru",
- "discords-glft.com",
- "discords-hypes.com",
- "discords-hypesquad.com",
- "discords-hypesquads.com",
- "discords-moderation.com",
- "discords-moderator.com",
- "discords-nitro.com",
- "discords-nitro.site",
- "discords-nitro.xyz",
- "discords-nitroapp.xyz",
- "discords-nitros.fun",
- "discords-nitros.shop",
- "discords-premium.com",
- "discords-premium.site",
- "discords-steam.com",
- "discords-support.com",
- "discords-teams.com",
- "discords.biz",
- "discords.co.uk",
- "discords.company",
- "discords.gifts",
- "discords.net",
- "discords.ru.com",
- "discords.ru",
- "discords.us",
- "discordsapi.com",
- "discordsapp.fun",
- "discordsapp.xyz",
- "discordsapplication.info",
- "discordsatus.com",
- "discordsearch.co",
- "discordservice.com",
- "discordsex.live",
- "discordsgift.com",
- "discordsgift.info",
- "discordshort.ga",
- "discordsite.repl.co",
- "discordsnitro.com",
- "discordsnitro.store",
- "discordsnitros.one",
- "discordspp.com",
- "discordss.ru",
- "discordstaff.xyz",
- "discordstat.com",
- "discordsteam.com",
- "discordsteam.ru",
- "discordsteams.com",
- "discordsub.com",
- "discordsupport.gg",
- "discordt.gift",
- "discordtest.xyz",
- "discordtesters.com",
- "discordtext.com",
- "discordtoken.com",
- "discordtokens.shop",
- "discordtokens2.repl.co",
- "discordtos.com",
- "discordtotal.com",
- "discordtotal.net",
- "discordtts.com",
- "discordtw.com",
- "discordu.gift",
- "discordup.ru",
- "discordx.link",
- "discordx.ml",
- "discordxgift.xyz",
- "discordxnitro.xyz",
- "discordxsteam.com",
- "discoredapp.com",
- "discorfd.com",
- "discorg.gg",
- "discorgift.online",
- "discorgift.xyz",
- "discorid.gift",
- "discoril.com",
- "discorl.com",
- "discorld-gift.site",
- "discorld.com",
- "discorld.site",
- "discorlgifts.store",
- "discorll.com",
- "discornd.com",
- "discorrd.com",
- "discorrd.gift",
- "discorrd.link",
- "discorrd.ru",
- "discorrd.site",
- "discorrdapp.com",
- "discorrl.com",
- "discorsd.com",
- "discorsd.gifts",
- "discort-nitro.com",
- "discort.com",
- "discort.site",
- "discortnitosteam.online",
- "discortnitostem.online",
- "discosd.com",
- "discosrd.com",
- "discotdapp.com",
- "discourd.com",
- "discourd.info",
- "discourd.site",
- "discourdapp.com",
- "discovd.com",
- "discpordapp.com",
- "discprd.com",
- "discqorcl.com",
- "discrd.co",
- "discrd.gg",
- "discrdapp.cf",
- "discrdapp.com",
- "discrds.gift",
- "discrdspp.com",
- "discrocl.xyz",
- "discrod-app.com",
- "discrod-app.ru",
- "discrod-app.site",
- "discrod-apps.ru",
- "discrod-gift.com",
- "discrod-gifte.com",
- "discrod-gifts.club",
- "discrod-glfts.com",
- "discrod-nitro.fun",
- "discrod-nitro.info",
- "discrod-up.ru",
- "discrod.gg",
- "discrod.gift",
- "discrod.gifts",
- "discrod.pw",
- "discrod.ru",
- "discrodapp.ru",
- "discrodapp.site",
- "discrodapp.xyz",
- "discrode-app.club",
- "discrode-app.com",
- "discrode-gift.club",
- "discrode-gift.com",
- "discrode-gifte.club",
- "discrode.gift",
- "discrodnitro.org",
- "discrodnitro.ru",
- "discrods.gift",
- "discrods.site",
- "discrodsteam.online",
- "discrodsteam.ru",
- "discrodup.ru",
- "discrord.com",
- "discrordapp.com",
- "discsord.com",
- "discsrdapp.com",
- "discurcd.com",
- "discurd.js.org",
- "discvordapp.com",
- "discxordapp.com",
- "disdrop.com.br",
- "disinfo.org.ru",
- "disiscord.com",
- "diskord.gg",
- "diskord.org.ru",
- "diskord.ru.com",
- "dislcord.com",
- "disocordapp.com",
- "disocr.com",
- "disocrd-gift.com",
- "disocrd-gift.ru",
- "disocrd.co",
- "disocrd.codes",
- "disocrd.gg",
- "disocrd.gifts",
- "disocrd.me",
- "disocrd.org",
- "disocrd.ru",
- "disocrd.tk",
- "disocrdapp.com",
- "disocrde.gift",
- "disocrds.gift",
- "disorc.com",
- "disord.co",
- "disord.codes",
- "disord.fun",
- "disord.gift",
- "disord.gifts",
- "disordapp.gift",
- "disordapp.gifts",
- "disorde.gift",
- "disordgift.codes",
- "disordgifts.com",
- "disordglft.com",
- "disordnitros.gifts",
- "disordnitros.xyz",
- "disordnltro.xyz",
- "disordnltros.com",
- "disordnltros.com",
- "disordnltros.gifts",
- "disords.gift",
- "disordsnitro.gifts",
- "disordsnitros.gifts",
- "disrcod.com",
- "disrcod.gift",
- "disrcod.gifts",
- "disrcord.com",
- "disscord.com",
- "disscord.gift",
- "disscord.online",
- "disscord.ru",
- "disscords.club",
- "dissord.com",
- "dissord.gift",
- "dissord.ru",
- "diswcord.com",
- "disxcord.com",
- "disxord.com",
- "diszcord.com",
- "diszcordapp.com",
- "diucord.js.org",
- "diuscordapp.com",
- "divinegardens.xyx",
- "diwcord.com",
- "dixcord.com",
- "dixscord.com",
- "dizcord.app",
- "dizcord.com",
- "dizcord.gift",
- "dizscord.com",
- "djiscord.com",
- "djscord.com",
- "dkscord.com",
- "dlcord.gift",
- "dlcsorcl.com",
- "dlcsorcl.ru",
- "dlcsord-airdrop.com",
- "dlcsord-gift.com",
- "dlicord-glfts.site",
- "dlicsord.ru",
- "dliscord-gift.com",
- "dliscord-gift.ru.com",
- "dliscord-gifts.com",
- "dliscord-giveaway.ru",
- "dliscord-glft.ru.com",
- "dliscord-nitro.com",
- "dliscord.com",
- "dliscord.gift",
- "dliscord.us",
- "dliscordl.com",
- "dliscordnltro.com",
- "dliscords.com",
- "dliscrd.one",
- "dlisocrd.ru",
- "dllscord.online",
- "dlscard.ru",
- "dlsccord-app.club",
- "dlsccord-apps.club",
- "dlsccrd.com",
- "dlscocrd.club",
- "dlscocrd.com",
- "dlscocrdapp.com",
- "dlscorcl-apps.com",
- "dlscorcl.gift",
- "dlscorcl.info",
- "dlscorcl.ru.com",
- "dlscorcl.ru",
- "dlscorcl.shop",
- "dlscorcl.xyz",
- "dlscorclapp.fun",
- "dlscord-alirdrop.com",
- "dlscord-alirdrop.site",
- "dlscord-app.com",
- "dlscord-app.info",
- "dlscord-app.net",
- "dlscord-app.ru.com",
- "dlscord-app.ru",
- "dlscord-app.su",
- "dlscord-app.xyz",
- "dlscord-apps.com",
- "dlscord-boost.fun",
- "dlscord-claim.com",
- "dlscord-developer.com",
- "dlscord-game.com",
- "dlscord-gift.com",
- "dlscord-gift.one",
- "dlscord-gift.ru.com",
- "dlscord-gift.xyz",
- "dlscord-gifts.com",
- "dlscord-gifts.xyz",
- "dlscord-glft.pw",
- "dlscord-glft.ru.com",
- "dlscord-glft.xyz",
- "dlscord-glfts.xyz",
- "dlscord-halloween.ru",
- "dlscord-hypesquad.com",
- "dlscord-hypesquads.com",
- "dlscord-inventory.fun",
- "dlscord-nitro.click",
- "dlscord-nitro.fun",
- "dlscord-nitro.info",
- "dlscord-nitro.link",
- "dlscord-nitro.ru.com",
- "dlscord-nitro.space",
- "dlscord-nitro.store",
- "dlscord-nltro.com",
- "dlscord-nltro.ru",
- "dlscord-nltro.xyz",
- "dlscord-promo.xyz",
- "dlscord-spooky.ru",
- "dlscord-steam.com",
- "dlscord-stime-2021.ru",
- "dlscord-store.club",
- "dlscord-support.com",
- "dlscord.app",
- "dlscord.art",
- "dlscord.blog",
- "dlscord.cc",
- "dlscord.click",
- "dlscord.cloud",
- "dlscord.fr",
- "dlscord.gg",
- "dlscord.gifts",
- "dlscord.in",
- "dlscord.info",
- "dlscord.ink",
- "dlscord.live",
- "dlscord.net",
- "dlscord.online",
- "dlscord.org",
- "dlscord.press",
- "dlscord.pro",
- "dlscord.rocks",
- "dlscord.ru.com",
- "dlscord.shop",
- "dlscord.site",
- "dlscord.space",
- "dlscord.store",
- "dlscord.support",
- "dlscord.team",
- "dlscord.tech",
- "dlscord.tips",
- "dlscord.wiki",
- "dlscord.world",
- "dlscordapp.codes",
- "dlscordapp.com",
- "dlscordapp.fun",
- "dlscordapp.info",
- "dlscordapp.pw",
- "dlscordapp.ru",
- "dlscordapp.store",
- "dlscordapps.com",
- "dlscordboost.com",
- "dlscordd.ru",
- "dlscordfull.ru",
- "dlscordgift.com",
- "dlscordgift.shop",
- "dlscordgived.xyz",
- "dlscordglft.xyz",
- "dlscordglfts.xyz",
- "dlscordniltro.com",
- "dlscordnitro.com",
- "dlscordnitro.info",
- "dlscordnitro.ru.com",
- "dlscordnitro.ru",
- "dlscordnitro.store",
- "dlscordnitro.us",
- "dlscordnitrofree.com",
- "dlscordnitros.gifts",
- "dlscordnltro.gifts",
- "dlscordnltro.online",
- "dlscordnltro.ru",
- "dlscordrglft.xyz",
- "dlscords.gifts",
- "dlscords.site",
- "dlscordsgift.xyz",
- "dlscordsglfts.xyz",
- "dlscordsream.pp.ua",
- "dlscordsteam.com",
- "dlscorldnitro.store",
- "dlscorp.com",
- "dlscors.gift",
- "dlscourd.info",
- "dlscrod-app.xyz",
- "dlscrod-game.ru",
- "dlscrod-gift.com",
- "dlscrod.ru.com",
- "dlscrodapp.ru",
- "dlsordnitro.gifts",
- "dlsordnltros.gifts",
- "dmarkef.com",
- "dmarket-place.pp.ua",
- "dmcordsteamnitro.de",
- "dnitrogive.com",
- "doatgiveaway.top",
- "does-small.ru.com",
- "dogewarrior-giveaway.info",
- "dola.pp.ua",
- "domineer.pp.ua",
- "dominosllc.com",
- "dominospizza-nl.com",
- "dominospizzanl.com",
- "dopeskins.com",
- "doscord.com",
- "doscordapp.com",
- "dota2fight.net",
- "dota2fight.ru",
- "dota2giveaway.top",
- "dota2giveaways.top",
- "dotacommunitu.xyz",
- "dotafights.vip",
- "dotagift01.xyz",
- "dotagift07.xyz",
- "dotagift11.xyz",
- "dotagift12.xyz",
- "dotagift13.xyz",
- "dotagift14.xyz",
- "dotagift15.xyz",
- "dotagiveaway.win",
- "douyutv.ru",
- "dragon-black.net.ru",
- "dragon-up.online",
- "dragonary-giveaway.info",
- "dreamhacks-fort.site",
- "dripa-discord.com",
- "driscord.ru.com",
- "driscord.ru",
- "dro-coad.ru",
- "drop-key.ru",
- "drop-nitro.com",
- "drop-nitro.fun",
- "drop-pro.com",
- "drop.net.ru",
- "drop.org.ru",
- "drop.pp.ru",
- "dropkeygood.ml",
- "drops4all.pp.ru",
- "dropskey.com",
- "dropskey.ru",
- "dropskin.monster",
- "drumairabubakar.com",
- "ds-nitr.xyz",
- "ds-nitro.com",
- "ds-nitro.site",
- "dscord-generaot.store",
- "dscord.gifts",
- "dscord.me",
- "dscord.nl",
- "dscord.xyz",
- "dscordapp.com",
- "dscordnitro.xyz",
- "dscrd.club",
- "dsctnitro.site",
- "dsicord.gift",
- "dsicrod.com",
- "dsiscord.com",
- "dsnitro.xyz",
- "duiscord.com",
- "dumdumdum.ru",
- "duscord.com",
- "duscord.js.org",
- "dwaynejon.xyz",
- "dwny.org",
- "dxiscord.com",
- "dzscord.js.org",
- "e-giftpremium.com",
- "ea-case.com",
- "ea-drop.com",
- "each-tel.xyz",
- "earnskinz.xyz",
- "easy-box.site",
- "easycases.pw",
- "easyopeningpay.online",
- "easyopeningpay.ru",
- "eazy-game.online",
- "eazy-game.ru",
- "eazydrop.monster",
- "ecnhasports.ru",
- "ecyber-tournament.ru",
- "ecyber-versus.ru",
- "egamerscup.club",
- "emeraldbets.ru",
- "en-roblox.com",
- "ence.net.ru",
- "encebrand.xyz",
- "encecsport.me",
- "encegun.xyz",
- "encesports.xyz",
- "enceteam.me",
- "enceteam.org.ru",
- "encewatch.ru",
- "epic-request.xyz",
- "epicfriendis.xyz",
- "epicfriennd.xyz",
- "epicgamees.xyz",
- "epicgamesnitro.com",
- "epicgamess.xyz",
- "epicgammes.xyz",
- "epicgamnes.xyz",
- "epicganmes.xyz",
- "epicggames.site",
- "epicggames.xyz",
- "epicinvite.xyz",
- "epicjames.xyz",
- "epickgames.xyz",
- "epicqames.xyz",
- "epicqannes.xyz",
- "epicservic.xyz",
- "epicservise.xyz",
- "epilcgames.xyz",
- "epiqgames.xyz",
- "eplcgames.xyz",
- "eplcups.com",
- "eplicgames.xyz",
- "eqiccames.xyz",
- "eqicgames.xyz",
- "esea-mdl.com",
- "esl-2020.com",
- "esl-drop.com",
- "esl-eu.com",
- "esl-gamingnetwork.com",
- "esl-gamingseries.com",
- "esl-lv.com",
- "esl-pl.com",
- "esl-playglobal.net",
- "esl-pro-legue.xyz",
- "esl-proleague.net",
- "eslcup.xyz",
- "eslgamescommunity.com",
- "eslgamesworldwide.com",
- "eslgaming-play.com",
- "eslgaming-world.com",
- "eslgamingnetworks.com",
- "eslgamingopen.com",
- "eslgamingworldwide.net",
- "eslhub.xyz",
- "eslhubgaming.com",
- "eslplaynetworks.com",
- "eslplayoneleague.com",
- "eslplayworlds.com",
- "eslpro.ru",
- "eslquickseries.com",
- "eslsports.ru",
- "eslworldwideplay.com",
- "esportgaming.ru",
- "esportgift.ru",
- "esportpoinl.xyz",
- "esportpoint.xyz",
- "esports-2go.pp.ua",
- "esports-csgo.ru",
- "esports-sale.ru",
- "esports-trade.net.ru",
- "esportscase.online",
- "esportscase.ru",
- "esportsfast.pp.ua",
- "esportsgvay.xyz",
- "esportsi.xyz",
- "espots-csgo.xyz",
- "essenseglow.com",
- "etsdrop.monster",
- "etssdrop.monster",
- "event-discord.com",
- "event-games4roll.com",
- "events-discord.com",
- "evmcups.ru",
- "ewqdsa.xyz",
- "exaltedbot.xyz",
- "exchangeuritems.gq",
- "explorerblocks.com",
- "extraskinscs.xyz",
- "ez-tasty.cyou",
- "ezcase.xyz",
- "ezclrop.ru",
- "ezdiscord.xyz",
- "ezdrop.net.ru",
- "ezdropss.net.ru",
- "ezdrp.ru",
- "ezopen.site",
- "ezpudge.pp.ua",
- "ezwin24.ru",
- "ezwithcounter.xyz",
- "ezzrun.pp.ua",
- "facecup.fun",
- "facedrop.one",
- "faceit-premium.com",
- "faceiteasyleague.ru",
- "faceiten.info",
- "facepunch-award.com",
- "facepunch-gifts.org.ru",
- "facepunch-llc.com",
- "facepunch-ltd.com",
- "facepunch-reward.com",
- "facepunch-studio.com",
- "facepunch-studio.us",
- "facepunch-twitch.com",
- "facepunchltd.com",
- "facepunchs.com",
- "facepunchskins.com",
- "facepunchstudio.com",
- "facerit.com",
- "faceuinuu.com",
- "faceuinuz.com",
- "faceuinuz.org.ru",
- "faceuinuz.ru.com",
- "fai-ceite.info",
- "faiceit.ru.com",
- "fall500.ru",
- "fang-operation.ru",
- "fannykey.ru",
- "farestonpw.ru.com",
- "faritkoko.ru",
- "farkimagix.xyz",
- "fartik.net.ru",
- "fasdf.pp.ua",
- "fast-cup.site",
- "fastcup.ru.com",
- "fastcups.xyz",
- "fastdrop.win",
- "fastgotournaments.xyz",
- "fastlucky.ru.com",
- "fastlucky.ru",
- "fastskins.ru",
- "fasttake.space",
- "fatown.net",
- "fdiscord.com",
- "ff.soul-ns.xyz",
- "fineleague.fun",
- "fineplay.xyz",
- "fireopencase.com",
- "firtonesroll.ru.com",
- "fiscord.com",
- "fivetown.net",
- "flyes-coin.com",
- "fnaatic.org.ru",
- "fnatcas.org.ru",
- "fnatic-2021.ru",
- "fnatic-drop.com",
- "fnatic-gg.fun",
- "fnatic-go.fun",
- "fnatic-ro1ls.ru.com",
- "fnatic-s.fun",
- "fnatic-team.ru",
- "fnatic-time.ru",
- "fnatic.pp.ru",
- "fnatic.team",
- "fnatic1.org.ru",
- "fnatic2.org.ru",
- "fnaticez.me",
- "fnaticforyou.xyz",
- "fnaticgit.xyz",
- "fnaticteam.org.ru",
- "fnnatic.org.ru",
- "fnnaticc.org.ru",
- "fntc-bd.pp.ua",
- "follow-ask.xyz",
- "forcedope.xyz",
- "forest-host.ru",
- "formulaprize.com",
- "fornite.best",
- "forse-pash.pp.ru",
- "forse-wash.pp.ru",
- "forsportss.pp.ua",
- "fortnight.space",
- "fortnite-newswapper.fun",
- "fortnite.sswapper.com",
- "fortnitebuy.com",
- "fortnitecrew.ru.com",
- "fortniteswapper.fun",
- "fortuneroll.tk",
- "fowephwo.ru",
- "foxycyber.ru",
- "fozzytournaments.fun",
- "fplgo.ru",
- "fps-booster.pw",
- "fr33item.xyz",
- "free-discord.ru",
- "free-dislcordnitrlos.ru",
- "free-niltross.ru",
- "free-nitlross.ru",
- "free-nitro-sus.pages.dev",
- "free-nitro.ru",
- "free-nitroi.ru",
- "free-nitros.ru",
- "free-skins.ru",
- "freediscord-nitro.cf",
- "freediscordnitro.ru",
- "freediscrodnitro.org",
- "freediskord-nitro.xyz",
- "freedrop0.xyz",
- "freefireclaim.club",
- "freeinstagramfollowersonline.com",
- "freenetflix.io",
- "freenitro.ru",
- "freenitrogenerator.cf",
- "freenitrogenerator.tk",
- "freenitroi.ru",
- "freenitrol.ru",
- "freenitros.com",
- "freenitros.ru",
- "freenitros.tk",
- "freenltro.ru",
- "freerobloxgenerator.tk",
- "freeskins.online",
- "freeskinsfree.pp.ua",
- "freespoty.com",
- "from-eliasae.ru.com",
- "from-puste.xyz",
- "from-sparsei.ru.com",
- "from-surenseds.xyz",
- "ftp.celerone.cf",
- "ftp.copyrighthelpbusiness.org",
- "ftp.def-dclss.pp.ua",
- "ftp.domineer.pp.ua",
- "ftp.fasdf.pp.ua",
- "ftp.ghostgame.ru",
- "ftp.gooditems.pp.ua",
- "ftp.greatdrops.pp.ua",
- "ftp.legasytour.it",
- "ftp.navieslproleagueseason13.pp.ua",
- "ftp.ogevtop.ru",
- "ftp.scogtopru.pp.ua",
- "ftp.steamcommunlty.it",
- "ftp.topeasyllucky.pp.ua",
- "ftp.versuscsgoplay.pp.ua",
- "fulldiscord.com",
- "funchest.fun",
- "fundro0p.site",
- "funjet1.ru.com",
- "funnydrop.store",
- "furtivhqqc.com",
- "furyesports.xyz",
- "furyleage.xyz",
- "fustcup.ru",
- "g-games.store",
- "g1veaway-nav1.site",
- "g2-cybersport.net",
- "g2-cybersport.ru",
- "g2-cybersports.net",
- "g2-esports.moscow",
- "g2-game.ru",
- "g2-give.info",
- "g2-give.ru",
- "g2-pro.shop",
- "g2a.ru.com",
- "g2cyber-espots.top",
- "g2cybergame.fun",
- "g2eref.ru",
- "g2ezports.xyz",
- "g2team-give.top",
- "g2team.org",
- "g2teams.com",
- "g2teamss.ru",
- "gaben-seller.pp.ua",
- "gamaloft.xyz",
- "gambit-cs.com",
- "gambit.net.ru",
- "gambit.org.ru",
- "gambitesports.me",
- "gambling1.ru.com",
- "gambling1.ru",
- "gamdom.ru",
- "game-case.ru",
- "game-csgo-steam.ru",
- "game-csgosteam.ru",
- "game-sense.space",
- "game-steam-csgo.ru",
- "game-steamcsgo.ru",
- "game-tournaments.net.ru",
- "game-tournaments.ru.com",
- "game.schweitzer.io",
- "game4roll.com",
- "gameb-platform.com",
- "gamecsgo-steam.ru",
- "gamegowin.xyz",
- "gamekere.net.ru",
- "gamekor.net.ru",
- "gameluck.ru",
- "gamemaker.net.ru",
- "gamepromo.net.ru",
- "gamerich.xyz",
- "gameroli.net.ru",
- "gamerolls.net.ru",
- "games-code.ru.com",
- "games-roll.ga",
- "games-roll.ml",
- "games-roll.ru",
- "gamesbuy.net.ru",
- "gamesfree.org.ru",
- "gamespol.net.ru",
- "gamzc-topz.xyz",
- "gamzgss-top.org.ru",
- "gamzgss-top.xyz",
- "garstel.github.io",
- "gave-nitro.com",
- "gavenitro.com",
- "gbauthorization.com",
- "gdiscord.com",
- "gdr-op.ru.com",
- "generator.discordnitrogift.com",
- "get-discord.fun",
- "get-gamesroll.xyz",
- "get-my-nitro.com",
- "get-nitro.com",
- "get-nitro.fun",
- "get-nitro.net",
- "get-traded.xyz",
- "get.sendmesamples.com",
- "getautomendpro.com",
- "getcach.monster",
- "getfitnos.com",
- "getfreediscordnitro.ml",
- "getnaturetonics.com",
- "getnitro.xyz",
- "getnitrogen.org",
- "getproviamax.com",
- "getriptide.live",
- "getskins.monster",
- "getstratuswatch.com",
- "getv-bucks.site",
- "getyouritems.pp.ua",
- "gfrtwgfkgc.xyz",
- "gg-dr0p.ru",
- "ggbolt.ru",
- "ggboom.ru",
- "ggdrop-gg.xyz",
- "ggdrop.org.ru",
- "ggdrop.pp.ru",
- "ggdrop.space",
- "ggdrop1.net.ru",
- "ggdrops.net.ru",
- "ggdrops.ru.com",
- "ggexpert.online",
- "ggexpert.ru",
- "ggfail.xyz",
- "gglootgood.xyz",
- "ggnatus.com",
- "ggnavincere.xyz",
- "ggtour.ru",
- "ghostgame.ru",
- "gif-discord.com",
- "gife-discorde.com",
- "gift-discord.online",
- "gift-discord.ru",
- "gift-discord.shop",
- "gift-discord.xyz",
- "gift-discords.com",
- "gift-g2.online",
- "gift-g2.ru",
- "gift-nitro.store",
- "gift4keys.com",
- "giftc-s.ru",
- "giftcsogg.ru",
- "giftdiscord.info",
- "giftdiscord.online",
- "giftes-discord.com",
- "giftnitro.space",
- "giftsdiscord.com",
- "giftsdiscord.fun",
- "giftsdiscord.online",
- "giftsdiscord.ru",
- "giftsdiscord.site",
- "givaeway.com",
- "givaewey.com",
- "giveavvay.com",
- "giveaway-fpl-navi.net.ru",
- "giveaway-fpl.net.ru",
- "giveawaynitro.com",
- "giveawayskin.com",
- "giveaweys.com",
- "giveeawayscin.me",
- "givenatus.site",
- "giveprize.ru",
- "giveweay.com",
- "givrayawards.xyz",
- "glaem.su",
- "gleam.su",
- "glets-nitro.com",
- "glft-discord.com",
- "glob21.online",
- "globacs.monster",
- "global-skins.gq",
- "globalcs.monster",
- "globalcss.monster",
- "globalcsskins.xyz",
- "globalmoestro.ru",
- "globalskins.tk",
- "gnswebservice.com",
- "go-cs.ru.com",
- "go-cups.ru",
- "go.rancah.com",
- "go.thefreedailyraffle.com",
- "go2-rush.pp.ua",
- "go4you.ru",
- "gocs8.ru.com",
- "gocs8q.ru",
- "gocs8v.ru.com",
- "gocsx.ru",
- "gocsx8.ru",
- "gocups.ru",
- "godssale.ru",
- "goldendota.com",
- "goman.ru.com",
- "good-csgo-steam.ru",
- "gooditems.pp.ua",
- "goodskins.gq",
- "gool-lex.org.ru",
- "gosteamanalyst.com",
- "great-drop.xyz",
- "greatdrops.pp.ua",
- "greatgreat.xyz",
- "greenwisedebtrelief.com",
- "gtakey.ru",
- "gtwoesport-battle.ru",
- "guardian-angel.xyz",
- "guns-slot.tk",
- "halitaoz.cam",
- "hallowen-nitro.com",
- "haste.monster",
- "hdiscord.com",
- "hdiscordapp.com",
- "hellcase.net.ru",
- "hellgiveaway.trade",
- "hellstorecoin.site",
- "hellstores.xyz",
- "help-center-portal.tk",
- "help.usabenefitsguide.com",
- "help.usalegalguide.com",
- "help.verified-badgeform.tk",
- "heroic-esports.ru",
- "hjoiaeoj.ru",
- "hltvcsgo.com",
- "hltvgames.net",
- "holyawards.xyz",
- "hope-nitro.com",
- "horizon-up.org.ru",
- "horizonup.ru",
- "hornetesports.xyz",
- "host322.ru",
- "howl.monster",
- "howls.monster",
- "httpdlscordnitro.ru.com",
- "humanlifeof.xyz",
- "humnchck.co",
- "hunts.monster",
- "huracancsgo.tk",
- "huyatv.ru",
- "hydra2018.ru",
- "hype-chat.ru",
- "hyper-tournament.xyz",
- "hypercups.ru",
- "hypertracked.com",
- "hyperz.monster",
- "id-374749.ru",
- "idchecker.xyz",
- "idealexplore.com",
- "idiscord.pro",
- "iemcup.com",
- "imvu37.blogspot.com",
- "in-gives.ru.com",
- "indereyn.ru.com",
- "information-discord.com",
- "inteledirect.com",
- "intimki.com",
- "into-nitro.com",
- "inventtop.com",
- "isp3.queryhost.ovh",
- "itemcloud.one",
- "iwinner.ru.com",
- "jet-crash.xyz",
- "jetcase.fun",
- "jetcase.ru.com",
- "jetscup.ru",
- "jjdiscord.com",
- "joewfpwg.ru",
- "jokedrop.ru",
- "jope-nitro.com",
- "joyskins.xyz",
- "juct-case.ru",
- "just-roll.ru",
- "justcase.net.ru",
- "justcause.fun",
- "justdior.com",
- "justwins.ru",
- "kahiotifa.ru",
- "kambol-go.ru",
- "kaspi-capital.com",
- "katowice.ru",
- "katowlce.ru",
- "kaysdrop.ru",
- "key-dr0b.com",
- "key-dr0p.com",
- "key-drcp.com",
- "key-drop-free.com",
- "key-dropo.com",
- "keydoppler.one",
- "keydorp.me",
- "keydrop.guru",
- "keydrop.org.ru",
- "keydrop.ru.com",
- "keydropp.one",
- "keydrops.xyz",
- "keydrup.ru",
- "keys-dropes.com",
- "keys-loot.com",
- "keysdropes.com",
- "kievskiyrosdachy-ua.ru",
- "kingofqueens2021.github.io",
- "kirakiooi.xyz",
- "kkgdrops.monster",
- "knife-eazy.pp.ua",
- "knifespin.top",
- "knifespin.xyz",
- "knifespins.xyz",
- "knifex.ru.com",
- "knifez-roll.xyz",
- "knifez-win.xyz",
- "knmirjdf.ru",
- "konicpirg.com",
- "kr1ks0w.ru",
- "kredo-capital.com",
- "ksgogift.pp.ua",
- "ksodkcvm.ru",
- "l0d4b860.justinstalledpanel.com",
- "l1568586.justinstalledpanel.com",
- "l23682ce.justinstalledpanel.com",
- "l3a32c23.justinstalledpanel.com",
- "l4a13998.justinstalledpanel.com",
- "l4bbc943.justinstalledpanel.com",
- "l95614b0.justinstalledpanel.com",
- "l9f009d3.justinstalledpanel.com",
- "la622566.justinstalledpanel.com",
- "la76c010.justinstalledpanel.com",
- "labfbb02.justinstalledpanel.com",
- "lakskuns.xyz",
- "lan-pro.fun",
- "lan-pro.link",
- "lan-pro.ru",
- "lan-pro.xyz",
- "lb4b95f8.justinstalledpanel.com",
- "lb6469d3.justinstalledpanel.com",
- "lb9d00fb.justinstalledpanel.com",
- "lbd74bef.justinstalledpanel.com",
- "lc995e52.justinstalledpanel.com",
- "lcb2f337.justinstalledpanel.com",
- "ld54d414.justinstalledpanel.com",
- "ldb9f474.justinstalledpanel.com",
- "ldiscord.gift",
- "ldiscordapp.com",
- "le491879.justinstalledpanel.com",
- "league-csgo.com",
- "legasytour.it",
- "lehatop-01.ru",
- "lemesports.ru",
- "lf4d4257.justinstalledpanel.com",
- "lf5d73bb.justinstalledpanel.com",
- "lfa90cb7.justinstalledpanel.com",
- "lfd0d93c.justinstalledpanel.com",
- "lifegg.xyz",
- "linktrade.pp.ua",
- "listycommunity.ru",
- "litenavi.xyz",
- "lkdiscord.com",
- "loginprofile.xyz",
- "loginrun.info",
- "longxrun.online",
- "loot-conveyor.com",
- "loot-item.xyz",
- "loot-rust.com",
- "loot.net.ru",
- "loot.pp.ru",
- "loot4fun.ru",
- "lootmake.com",
- "lootship.ga",
- "lootshunt.org.ru",
- "lootsrow.com",
- "lootxmarket.com",
- "loungeztrade.com",
- "low-cups.ru",
- "lozt.pp.ua",
- "luancort.com",
- "lucky-skins.xyz",
- "luckycrush.ga",
- "luckydrop.site",
- "luckyfast.ru.com",
- "luckyfast.ru",
- "luckygift.net.ru",
- "luckygift.space",
- "luckygo.ru.com",
- "luckygo.ru",
- "luckyiwin.ml",
- "luckyiwin.tk",
- "luxace.ru.com",
- "luxerkils.xyz",
- "m-discord.pw",
- "m.setampowered.com",
- "m90694rb.beget.tech",
- "made-nitro.com",
- "madessk.pp.ua",
- "maggicdrop.xyz",
- "magic-delfy.net.ru",
- "magicdropgift.ru",
- "magicdropnew.xyz",
- "magicrollslg.com.ru",
- "magicrollslw.com.ru",
- "magicroulete.ru",
- "magicrun.site",
- "magictop.ru.com",
- "magifcrolrlc.xyz",
- "magifcrolrlh.xyz",
- "magifrolbiq.xyz",
- "magifrolbit.xyz",
- "magik-dr0p.fun",
- "magikbrop.xyz",
- "magnaviroll.xyz",
- "magnavirolls.xyz",
- "magnavirollz.xyz",
- "mail.celerone.cf",
- "mail.csgoroll.ru",
- "mail.dicsord-airdrop.ru",
- "mail.explorerblocks.com",
- "mail.fasdf.pp.ua",
- "mail.ghostgame.ru",
- "mail.gooditems.pp.ua",
- "mail.ogevtop.ru",
- "mail.scogtopru.pp.ua",
- "mail.streamcomuniity.pp.ua",
- "mail.versuscsgoplay.pp.ua",
- "majestictips.com",
- "major-2021.ru",
- "makson-gta.ru",
- "malibones.buzz",
- "marke-tcgo.ru.com",
- "marke-tgo.ru.com",
- "market-csgo.ru",
- "market-subito.site",
- "marketsleam.xyz",
- "marketsm.pp.ua",
- "markt-csgo.ru.com",
- "markt-csru.info",
- "marktcsgo.ru.com",
- "mars-cup.ru",
- "master-up.ru",
- "maxskins.xyz",
- "mcdaonlds.com",
- "mcdelivery-offer.com",
- "mcdelivery-sale.com",
- "mcdelivery24.com",
- "mcdonalds-iloveit.com",
- "mcdonalds-saudiarabia.com",
- "mcdonaldsau.info",
- "mdiscord.com",
- "medpatrik.ru",
- "megacase.monster",
- "mekaverse-minting.com",
- "mekaversecollection.com",
- "mekaversenft.net",
- "microsup.net",
- "minea.club",
- "moderationacademy-exams.com",
- "mol4a.pp.ua",
- "money.fastcreditmatch.com",
- "money.usacashfinder.com",
- "mvcsgo.com",
- "mvpcup.ru",
- "mvptournament.com",
- "my-trade-link.ru",
- "my-tradelink.ru",
- "myccgo.xyz",
- "mychaelknight.com",
- "mycsgoo.ru",
- "mydrop.monster",
- "myfast.ru",
- "mygames4roll.com",
- "myjustcase.ru",
- "myrolls.monster",
- "myrollz.com",
- "mythic-esports.xyz",
- "mythiccups.xyz",
- "mythicleagues.xyz",
- "mythicups.xyz",
- "myticks.xyz",
- "mytrade-link.ru.com",
- "mytradelink.pp.ua",
- "mytradelink.ru.com",
- "mytradeoffers.ru.com",
- "nacybersportvi.ru",
- "nagipen.ru",
- "nagiver.ru",
- "naturespashowerpurifier.com",
- "natus-lootbox.net.ru",
- "natus-lootbox.org.ru",
- "natus-open.net.ru",
- "natus-open.org.ru",
- "natus-open.pp.ru",
- "natus-rolls.xyz",
- "natus-space.ru",
- "natus-spot.net.ru",
- "natus-spot.pp.ru",
- "natus-vincere.ru",
- "natus-vincere.space",
- "natus-vincere.xyz",
- "natus-vincery-majors.ru.com",
- "natus-vincerygive.xyz",
- "natus-vincerygivess.xyz",
- "natus-vincerygivesz.xyz",
- "natus-vincerygivex.xyz",
- "natus-vincerygivezc.xyz",
- "natus-vincerygivezr.ru",
- "natus-vincerygivezz.xyz",
- "natus-win.net.ru",
- "natus-win.org.ru",
- "natus-win.pp.ru",
- "natusforyou.pp.ua",
- "natusspot.pp.ru",
- "natustop.net.ru",
- "natustop.org.ru",
- "natusvincerbestmarket.work",
- "natusvinceredrop.ru",
- "natuswin.org.ru",
- "nav-s1.ru",
- "navi-21.ru",
- "navi-bp.com",
- "navi-cis.net.ru",
- "navi-cs.com",
- "navi-drop.net",
- "navi-drop2020.com",
- "navi-es.ru",
- "navi-esl.ru.com",
- "navi-esports.net",
- "navi-eu.ru",
- "navi-ez.com",
- "navi-freedrop.xyz",
- "navi-freeskins.com",
- "navi-give.net.ru",
- "navi-giveaway-simple.net.ru",
- "navi-giveaway.net",
- "navi-giveaway.xyz",
- "navi-gs.com",
- "navi-gt.com",
- "navi-gv.com",
- "navi-hawai.net.ru",
- "navi-io.com",
- "navi-keep.net.ru",
- "navi-lix.xyz",
- "navi-ls.com",
- "navi-lzx.ru",
- "navi-off.us",
- "navi-ol.com",
- "navi-q.com",
- "navi-rt.com",
- "navi-russia.ru",
- "navi-share.pp.ru",
- "navi-skins.org.ru",
- "navi-skins.pp.ru",
- "navi-sp.com",
- "navi-tm.com",
- "navi-tm.ru",
- "navi-up.com",
- "navi-up.ru",
- "navi-winners.org.ru",
- "navi-wins-skiins.org.ru",
- "navi-x.ru",
- "navi-youtube.net.ru",
- "navi.pp.ru",
- "navi2021.net.ru",
- "naviback.ru",
- "navibase.net.ru",
- "navibase.org.ru",
- "navibase.pp.ru",
- "navicase-2020.org.ru",
- "navicase.org",
- "navicsg.ru",
- "navidonative.ru",
- "naviend.xyz",
- "navieslproleagueseason13.pp.ua",
- "naviesport.net",
- "naviesportsgiveaways.pro",
- "navifree.ru",
- "navifreeskins.ru",
- "navifun.me",
- "navigg.org.ru",
- "navigg.ru",
- "naviggcoronagiveaway.ru",
- "navigiveaway.ru",
- "navign.me",
- "navigs.ru",
- "navileague.xyz",
- "navination.site",
- "navipodarok.ru",
- "navipresent.xyz",
- "naviqq.org.ru",
- "navirolls.org.ru",
- "navishare.net.ru",
- "navishare.pp.ru",
- "naviskins.xyz",
- "naviteam.net.ru",
- "naviteamway.net.ru",
- "navitm.ru",
- "navvigg.site",
- "navviigg.ru",
- "navy-freecases.ru",
- "navy-loot.xyz",
- "nawegate.com",
- "nawi-gw.ru",
- "nawibest.ru.com",
- "nawigiveavay.xyz",
- "netfllix-de.com",
- "new-collects.xyz",
- "new-drop.net.ru",
- "new-offer.trade",
- "new-steamcommunlty.xyz",
- "new.mychaelknight.com",
- "newdiscord.online",
- "nice-haesh-info.ru",
- "nicegg.ru",
- "night-skins.com",
- "nightz.monster",
- "nise-cell.net.ru",
- "nise-gell.org.ru",
- "nise-well.org.ru",
- "nise-win.xyz",
- "nitrlooss-free.ru",
- "nitro-airdrop.org",
- "nitro-all.xyz",
- "nitro-app.com",
- "nitro-app.fun",
- "nitro-discord.fun",
- "nitro-discord.info",
- "nitro-discord.me",
- "nitro-discord.org",
- "nitro-discord.ru.com",
- "nitro-discordapp.com",
- "nitro-discords.com",
- "nitro-drop.com",
- "nitro-ds.xyz",
- "nitro-for-free.com",
- "nitro-from-steam.com",
- "nitro-gift.ru.com",
- "nitro-gift.ru",
- "nitro-gift.site",
- "nitro-gift.space",
- "nitro-gift.store",
- "nitro-gift.xyz",
- "nitro-give.site",
- "nitro-up.com",
- "nitro.gift",
- "nitroairdrop.com",
- "nitroappstore.com",
- "nitrochallange.com",
- "nitrodiscord.org",
- "nitrodlscordl.xyz",
- "nitrodlscordx.xyz",
- "nitrofgift.xyz",
- "nitrofrees.ru",
- "nitrogeneral.ru",
- "nitrogift.xyz",
- "nitrogive.com",
- "nitroos-frieie.ru",
- "nitroosfree.ru",
- "nitropussy.com",
- "nitros-gift.com",
- "nitrostore.org",
- "nitrotypehack.club",
- "nltro.site",
- "ns1.dns-soul.wtf",
- "ns1.dropc.me",
- "ns1.navitry.me",
- "ns1.peektournament.me",
- "ns2.dropc.me",
- "ns2.helpform-center.ml",
- "nur-electro-05.ml",
- "nv-pick.com",
- "nvcontest.xyz",
- "nwgwroqr.ru",
- "offerdealstop.com",
- "official-nitro.com",
- "official-nitro.fun",
- "ogevtop.ru",
- "ogfefieibio.ru",
- "okdiscord.com",
- "oligarph.club",
- "onehave.xyz",
- "open-case.work",
- "opencase.space",
- "operation-broken.xyz",
- "operation-pass.ru.com",
- "operation-riptide.link",
- "operation-riptide.ru.com",
- "operation-riptide.xyz",
- "operationbroken.xyz",
- "operationreptide.com",
- "operationriptide.tk",
- "opinionshareresearch.com",
- "order-40.com",
- "order-78.com",
- "order-87.com",
- "order-96.com",
- "orderpropods.com",
- "ornenaui.ru",
- "out-want.xyz",
- "output-nitro.com",
- "overdrivsa.xyz",
- "ovshau.club",
- "ownerbets.com",
- "p.t67.me",
- "paayar.info",
- "pandakey.ru",
- "pandaskin.ru.com",
- "pandaskins.ru.com",
- "pandemidestekpaket.cf",
- "passjoz.net.ru",
- "path.shareyourfreebies.com",
- "path.topsurveystoday.com",
- "patrool.net.ru",
- "pay-18.info",
- "payeaer.xyz",
- "payear.xyz",
- "payeer.life",
- "payeer.live",
- "payeer.vip",
- "pingagency.ru",
- "pizzaeria-papajohns.com",
- "playcsgo-steam.ru",
- "playerskinz.xyz",
- "playeslseries.com",
- "please.net.ru",
- "pltw.com",
- "pluswin.ru",
- "pluswsports.ru",
- "poloname.net.ru",
- "pop.ghostgame.ru",
- "pop.ogevtop.ru",
- "pose1dwin.ru",
- "poste.xyz",
- "power-sk1n.net.ru",
- "ppayeer.ru.com",
- "ppayeer.ru",
- "prajyoth-reddy-mothi.github.io",
- "prajyoth.me",
- "prefix.net.ru",
- "premium-discord.com",
- "premium-discords.com",
- "premium-faceit.com",
- "premiums-discord.com",
- "price-claim.xyz",
- "prime-drop.xyz",
- "privatexplore.com",
- "privatkeyblok.com",
- "prizee-good.com",
- "profile-2994292.ru",
- "profile-442572242.online",
- "profiles-7685291049068.me",
- "promo-codes.world",
- "promo-discord.com",
- "promo-discord.site",
- "proz.monster",
- "psyonix-trade.online",
- "psyonix.website",
- "psyonlxcodes.com",
- "ptbdiscord.com",
- "pubg-asia.xyz",
- "pubg-steamcommunityyz.top",
- "pubg.network",
- "pubg.new-collects.xyz",
- "pubgclaims.com",
- "pubge21.xyz",
- "pubgfree77.com",
- "pubgfreedownload.org",
- "pubgfreeeus.cf",
- "pubggf01.xyz",
- "pubggf02.xyz",
- "pubggf03.xyz",
- "pubggf04.xyz",
- "pubggf05.xyz",
- "pubggf06.xyz",
- "pubggf10.xyz",
- "pubggf15.xyz",
- "pubggf16.xyz",
- "pubggf17.xyz",
- "pubggf18.xyz",
- "pubggf19.xyz",
- "pubggf20.xyz",
- "pubggf21.xyz",
- "pubggf22.xyz",
- "pubggf23.xyz",
- "pubggf24.xyz",
- "pubggf25.xyz",
- "pubggf26.xyz",
- "pubggf27.xyz",
- "pubggf28.xyz",
- "pubggf29.xyz",
- "pubggf30.xyz",
- "pubggf31.xyz",
- "pubggf32.xyz",
- "pubggf33.xyz",
- "pubggf34.xyz",
- "pubggf35.xyz",
- "pubggf36.xyz",
- "pubggf37.xyz",
- "pubggf38.xyz",
- "pubggf39.xyz",
- "pubggf40.xyz",
- "pubggf41.xyz",
- "pubggf42.xyz",
- "pubggift100.xyz",
- "pubggift101.xyz",
- "pubggift102.xyz",
- "pubggift31.xyz",
- "pubggift32.xyz",
- "pubggift48.xyz",
- "pubggift56.xyz",
- "pubggift58.xyz",
- "pubggift59.xyz",
- "pubggift60.xyz",
- "pubggift61.xyz",
- "pubggift62.xyz",
- "pubggift63.xyz",
- "pubggift64.xyz",
- "pubggift65.xyz",
- "pubggift66.xyz",
- "pubggift67.xyz",
- "pubggift68.xyz",
- "pubggift69.xyz",
- "pubggift70.xyz",
- "pubggift71.xyz",
- "pubggift87.xyz",
- "pubggift91.xyz",
- "pubggift92.xyz",
- "pubggift93.xyz",
- "pubggift94.xyz",
- "pubggift95.xyz",
- "pubggift96.xyz",
- "pubggift97.xyz",
- "pubggift98.xyz",
- "pubggift99.xyz",
- "pubgmcheats.com",
- "pubgmobile2019ucfreeeee.tk",
- "pubgmobile365.com",
- "pubgmobile365.giftcodehot.net",
- "pubgmobile737373.ml",
- "pubgmobileskin2020.com",
- "pubgmobilespro.my.id",
- "pubgmobileuc2020free.cf",
- "pubgofficielbcseller.online",
- "pubgtoken.io",
- "pubguccmobilefree.cf",
- "qbt-giveaway.info",
- "qcold.club",
- "qcoldteam.life",
- "qtteddybear.com",
- "quantumtac.co",
- "quick-cup.xyz",
- "quickrobux.net",
- "r-andomfloat.ru",
- "rainorshine.ru",
- "ran-getto.org.ru",
- "rangskins.com",
- "rave-clup.ru",
- "rave-new.ru",
- "rblxcorp.work",
- "rbux88.com",
- "rbux88go.com",
- "rdr2code.ru",
- "realskins.xyz",
- "realtorg.xyz",
- "redirectednet.xyz",
- "redizzz.xyz",
- "rednance.com",
- "redskin.monster",
- "reports.noodlesawp.ru",
- "reslike.net",
- "rewardbuddy.me",
- "rewards-rl.com",
- "rewardsavenue.net",
- "rewardspremium-nitro.gq",
- "rien.xyz",
- "rip-tide.ru",
- "ripetide.ru",
- "riptid-operation.ru",
- "riptide-cs.com",
- "riptide-cs.ru",
- "riptide-csgo.ru",
- "riptide-free-pass.net.ru",
- "riptide-free-pass.org.ru",
- "riptide-free-pass.pp.ru",
- "riptide-gaming.ru",
- "riptide-operation.com",
- "riptide-operation.ru.com",
- "riptide-operation.ru",
- "riptide-operation.xyz",
- "riptide-operations.ru",
- "riptide-pass.org.ru",
- "riptide-take.ru",
- "riptide-valve.ru",
- "riptidefree.ru",
- "riptiden.ru",
- "riptideoffer.ru",
- "riptideoperation.xyz",
- "riptidepass.net.ru",
- "riptidepass.ru",
- "rl-activate.com",
- "rl-award.com",
- "rl-bounce.com",
- "rl-change.ru",
- "rl-chaser.com",
- "rl-code.com",
- "rl-diamond.com",
- "rl-epic.com",
- "rl-fandrops.com",
- "rl-fanprize.com",
- "rl-fast.com",
- "rl-fastrading.com",
- "rl-garage.info",
- "rl-garage.online",
- "rl-garage.space",
- "rl-give.ru.com",
- "rl-insidergift.com",
- "rl-performance.com",
- "rl-positive.com",
- "rl-promocode.com",
- "rl-promos.com",
- "rl-purple.com",
- "rl-retail.fun",
- "rl-rewards.ru.com",
- "rl-tracking.pro",
- "rl-traders.com",
- "rlatracker.com",
- "rlatracker.pro",
- "rldrop-gifts.com",
- "rldrop.gifts",
- "rlexcihnage.com",
- "rlgarages.com",
- "rlgifts.org",
- "rlgtracker.zone",
- "rlq-trading.com",
- "rlqtrading.com",
- "rlshop.fun",
- "rlstracker.com",
- "rltracken.ru",
- "rltrackings.com",
- "rlv-trading.com",
- "rlz-trading.com",
- "robfan.work",
- "roblox-collect.com",
- "roblox-login.com",
- "roblox-porn.com",
- "roblox-robux.de",
- "roblox.com.so",
- "roblox.free.robux.page",
- "roblox.help",
- "roblox.link.club",
- "robloxbing.com",
- "robloxdownload.org",
- "robloxgamecode.com",
- "robloxgiftcardz.com",
- "robloxpasssword.com",
- "robloxromania.com",
- "robloxs.land",
- "robloxsecure.com",
- "robloxstore.co.uk",
- "robloxux.com",
- "robloxwheelspin.com",
- "robloxxhacks.co",
- "robuux1.club",
- "robux-codes.ga",
- "robux.claimgifts.shop",
- "robux20.club",
- "robux247.win",
- "robux4sex.tk",
- "robuxat.com",
- "robuxfiends.com",
- "robuxfree.us",
- "robuxgen.site",
- "robuxhach.com",
- "robuxhelp.com",
- "robuxhelpers.com",
- "robuxhelps.com",
- "robuxprofiles.com",
- "robuxtools.me",
- "robuxx.work",
- "robx.pw",
- "rocket-dealer.com",
- "rocket-item.com",
- "rocket-leag.com",
- "rocket-league.info",
- "rocket-retailer.fun",
- "rocket-tournament.fun",
- "rocket-trader.fun",
- "rocket-traders.store",
- "rocket-trades.store",
- "rocket-trading.site",
- "rocket-trading.space",
- "rocket-trading.store",
- "rocket-tradings.com",
- "rocket2pass.com",
- "rocketleague-drops.com",
- "rocketleagues.site",
- "rocketleaque.info",
- "rocketradings.com",
- "rockets-garages.com",
- "rockets-item.com",
- "rockets-items.com",
- "rockets-sale.com",
- "rockets-sales.com",
- "rockets-trade.com",
- "roleum.buzz",
- "roll-gift.fun",
- "roll-skins.ga",
- "roll-skins.ru",
- "roll-skins.tk",
- "roll-statedrop.ru",
- "roll4knife.xyz",
- "roll4tune.com",
- "rollcas.ru.com",
- "rollgame.net.ru",
- "rollkey.ru.com",
- "rollknfez.xyz",
- "rollskin-simple.xyz",
- "rollskin.ru",
- "rollskins.monster",
- "rollskins.ru",
- "rool-skins.xyz",
- "roposp12.design",
- "roposp14.design",
- "ropost15.xyz",
- "roulette-prizes.ru.com",
- "roulettebk.ru",
- "royalegive.pp.ua",
- "run2go.ru",
- "runwebsite.ru",
- "rushbskins.xyz",
- "rushskillz.net.ru",
- "rushskins.xyz",
- "rust-award.com",
- "rust-boom.xyz",
- "rust-charge.com",
- "rust-chest.com",
- "rust-code.com",
- "rust-code.ru.com",
- "rust-codes.com",
- "rust-drop.ru.com",
- "rust-get.com",
- "rust-gitfs.ru",
- "rust-giveaways.xyz",
- "rust-kit.com",
- "rust-llc.com",
- "rust-ltd.com",
- "rust-reward.com",
- "rust-satchel.com",
- "rust-skin.com",
- "rust.facepunchs.com",
- "rustarea.me",
- "rustg1ft.com",
- "rustg1fts.online",
- "rustg1fts.ru",
- "rustgame-servers.com",
- "rustprize.com",
- "rustygift.site",
- "rustyit-ems.xyz",
- "s-steame.ru",
- "s-teame.ru",
- "s1cases.site",
- "s1cses.site",
- "s1mple-give-away.pp.ua",
- "s1mple-spin.xyz",
- "s1mplesun.design",
- "s92673tu.beget.tech",
- "sa-mcdonalds.com",
- "safe-funds.site",
- "said-home.ru.com",
- "sakuralive.ru.com",
- "sale-steampowered.com",
- "savage-growplus.com",
- "scale-navi.pp.ru",
- "scl-online.ru",
- "sclt.xyz",
- "scltourments.xyz",
- "scogtopru.pp.ua",
- "scteamcommunity.com",
- "scwanmei.ru",
- "sdiscord.com",
- "seamcommunity.com",
- "seamconmunity.xyz",
- "seancommunity.com",
- "seancommunlty.ru",
- "secure-instagram.ru",
- "secure.yourreadytogoproduct.surf",
- "seed-nitro.com",
- "services.runescape.rs-tt.xyz",
- "services.runescape.rs-ui.xyz",
- "setamcommunity.com",
- "shadowmarket.xyz",
- "shadowpay.pp.ru",
- "share.nowblox.com",
- "shattereddrop.xyz",
- "shib.events",
- "shimermsc.ru",
- "shopy-nitro.tk",
- "shroud-cs.com",
- "sieamcommunity.net.ru",
- "sieamcommunity.org.ru",
- "simple-knifez.xyz",
- "simple-win.xyz",
- "simplegamepro.ru",
- "simplegif.ru",
- "simpleroll-cs.xyz",
- "simplespinz.xyz",
- "simplewinz.xyz",
- "siriusturnier.pp.ua",
- "sitemap.onedrrive.com",
- "skill-toom.pp.ru",
- "skin-index.com",
- "skin888trade.com",
- "skincs-spin.top",
- "skincs-spin.xyz",
- "skincsggtl.xyz",
- "skindeyyes.ru",
- "skingstgg.ru",
- "skingstgo.ru",
- "skini-lords.net.ru",
- "skinkeens.xyz",
- "skinmarkets.net",
- "skinnprojet.ru",
- "skinpowcs.ru",
- "skinpowst.ru",
- "skinroll.ru.com",
- "skinroll.ru",
- "skins-drop.ru",
- "skins-hub.top",
- "skins-info.net",
- "skins-jungle.xyz",
- "skins-navi.pp.ru",
- "skins.net.ru",
- "skins.org.ru",
- "skins.pp.ru",
- "skins1wallet.xyz",
- "skinsbon.com",
- "skinsboost.ru",
- "skinscsanalyst.ru",
- "skinsdatabse.com",
- "skinsgo.monster",
- "skinsind.com",
- "skinslit.com",
- "skinsmedia.com",
- "skinsmind.ru",
- "skinspace.ru",
- "skinsplane.com",
- "skinsplanes.com",
- "skinsplanets.com",
- "skinstradehub.com",
- "skinsup.monster",
- "skinup.monster",
- "skinxinfo.net",
- "skinxmarket.site",
- "skinz-spin.top",
- "skinz-spin.xyz",
- "skinzjar.ru",
- "skinzprize.xyz",
- "skinzspin-cs.xyz",
- "skinzspinz.xyz",
- "sklinsbaron.net",
- "sl1pyymyacc.ru",
- "slaaeamcrommunity.com.profiles-7685291049068.me",
- "sleam-trade.net.ru",
- "sleam-trade.org.ru",
- "sleam-trade.pp.ru",
- "sleamcominnuty.ru",
- "sleamcommiinuty.ru",
- "sleamcomminity.ru",
- "sleamcomminutiycom.ru.com",
- "sleamcommmunily.xyz",
- "sleamcommmunitiy.ru",
- "sleamcommmunity.com",
- "sleamcommmuntiy.ru",
- "sleamcommnnity.com",
- "sleamcommnunity.net",
- "sleamcommuiliy.ru.com",
- "sleamcommuinity.xyz",
- "sleamcommuintiy.ru.com",
- "sleamcommuinty.store",
- "sleamcommuity.com",
- "sleamcommunety.ru",
- "sleamcommuniitey.ru.com",
- "sleamcommuniity.me",
- "sleamcommuniity.ru.com",
- "sleamcommuniity.xyz",
- "sleamcommuniiy.ru",
- "sleamcommunilly.me",
- "sleamcommunilly.ru",
- "sleamcommunily.net",
- "sleamcommunily.org",
- "sleamcommunily.ru.com",
- "sleamcommuninty.com",
- "sleamcommuninty.ru",
- "sleamcommuniry.ru",
- "sleamcommunitey.com",
- "sleamcommuniti.ru",
- "sleamcommuniti.xyz",
- "sleamcommunitiy.com",
- "sleamcommunitty.xyz",
- "sleamcommunittyy.me",
- "sleamcommunitu.net.ru",
- "sleamcommunitu.ru",
- "sleamcommunituy.com",
- "sleamcommunity.me",
- "sleamcommunity.net",
- "sleamcommunity.org.ru",
- "sleamcommunity.org",
- "sleamcommunity.pp.ru",
- "sleamcommunityprofiles76561199056426944.ru",
- "sleamcommunityy.me",
- "sleamcommunlity.xyz",
- "sleamcommunlty.net.ru",
- "sleamcommunlty.net",
- "sleamcommunlty.ru.com",
- "sleamcommunlty.space",
- "sleamcommunlty.xyz",
- "sleamcommunnitu.com",
- "sleamcommunnity.net",
- "sleamcommunnity.org",
- "sleamcommunnity.ru",
- "sleamcommuntiny.ru",
- "sleamcommuntity.ru",
- "sleamcommuntiy.com",
- "sleamcommuntly.ru",
- "sleamcommunty.com",
- "sleamcommunyti.ru",
- "sleamcommunytu.ru",
- "sleamcommutiny.com",
- "sleamcommuunity.com",
- "sleamcommynilu.online",
- "sleamcommynitu.ru",
- "sleamcommynity.ru",
- "sleamcommyunity.com",
- "sleamcomnnuniity.ru",
- "sleamcomnnuniliy.site",
- "sleamcomnnunily.site",
- "sleamcomnnunily.website",
- "sleamcomnnunitiy.ru",
- "sleamcomnnunity.ru",
- "sleamcomnnunty.website",
- "sleamcomnumity.com",
- "sleamcomnunily.ru",
- "sleamcomnunity.net.ru",
- "sleamcomnunity.xyz",
- "sleamcomnunlty.me",
- "sleamcomrnunity.com",
- "sleamcomuniity.ru",
- "sleamcomunitly.co",
- "sleamcomunity.me",
- "sleamcomunity.net.ru",
- "sleamcomunity.ru.com",
- "sleamcomunuty.ru",
- "sleamconmumity.com",
- "sleamconmunity.ru",
- "sleamconmunity.xyz",
- "sleamconmunlity.com",
- "sleamconmunnity.com",
- "sleamconnmunitiy.com",
- "sleamconnunity.net.ru",
- "sleamconnunity.net",
- "sleamcoommunilty.com",
- "sleamcoommunily.com",
- "sleamcoommunity.com",
- "sleamcoommunlilty.com",
- "sleamcoommunlity.com",
- "sleamcoomnnunity.xyz",
- "sleamcoomunity.com",
- "sleamcoomuuntty.xyz",
- "sleamcornmunuity.me",
- "sleamcornmunyti.ru",
- "sleamcornrnunity.host",
- "sleamcornrnunity.ru",
- "sleamcummunity.me",
- "sleammcommunity.ru",
- "sleammcommunnity.ru",
- "sleampowered.com",
- "sleampowereed.ru",
- "sleamscommunity.com",
- "sleamtrade-offer.xyz",
- "sleancommunlty.xyz",
- "sleancomninity.xyz",
- "sleanmconmunltiy.ru",
- "slearncommunity.store",
- "sleemcomnuniti.xyz",
- "sleepbuster.xyz",
- "slemcamunity.ru",
- "slemcommunity.com",
- "slemommunity.com",
- "sleramconnummitti.org",
- "slreamcommumnlty.com",
- "slreamcommunntiy.org",
- "slreamcomnuitly.xyz",
- "slreamcomunity.ru",
- "slreamcomunntiy.org",
- "slteamcommuinity.com",
- "slteamcommunity.com",
- "slteamconmuniity.com",
- "slum-trade.org.ru",
- "smartcommunity.net",
- "smeacommunity.com.au",
- "smitecommunity.org",
- "smtp.ghostgame.ru",
- "smtp.ogevtop.ru",
- "softhack.ru",
- "some-other.ru.com",
- "sometheir.xyz",
- "sp708431.sitebeat.site",
- "spacegivewayzr.xyz",
- "spacegivewayzw.xyz",
- "special4u.xyz",
- "speedtrkzone.com",
- "spin-games.com",
- "spin4skinzcs.top",
- "spin4skinzcs.xyz",
- "spinforskin.ml",
- "spiritsport.xyz",
- "sponsored-simple.xyz",
- "sports-liquid.com",
- "spt-night.ru",
- "sreamcomminity.ru",
- "sreamcommuniity.com",
- "sreamcommunity.com",
- "sreamcommunity.net.ru",
- "sreamcommunity.org.ru",
- "sreamcommunty.com",
- "sreammcommuunntileiy.xyz",
- "sreampowered.com",
- "sreancomunllty.xyz",
- "srtreamcomuninitiy.xyz",
- "ssteamcommunitry.com",
- "ssteamcommunity.com",
- "ssteamcommunity.ru.com",
- "ssteampowered.com",
- "st-csgo.ru",
- "st-eam.ru",
- "staamcommunity.com",
- "staeaemcornmunite.me",
- "staeamcomunnityu.me",
- "staeamconmuninty.me",
- "staeamconnunitly.online",
- "staeamconnunitly.ru",
- "staeamcromnuninty.com.profiles-76582109509.me",
- "staem-communitu.info",
- "staemcammunity.com",
- "staemcammunity.me",
- "staemcammynlty.ru",
- "staemccommunnity.net.ru",
- "staemcomcommunlty.ru.com",
- "staemcomcommunlty.ru",
- "staemcomconmunlty.ru.com",
- "staemcommintu.ru",
- "staemcomminuty.online",
- "staemcomminuty.ru",
- "staemcommmunity.com",
- "staemcommmunity.online",
- "staemcommmunity.ru",
- "staemcommnity.ru",
- "staemcommnuniti.com",
- "staemcommnunity.ru.com",
- "staemcommnutiy.ru",
- "staemcommueneity.com",
- "staemcommuinity.com",
- "staemcommuneaity.com",
- "staemcommunety.com",
- "staemcommuneuity.com",
- "staemcommuniity.com",
- "staemcommunility.com",
- "staemcommunily.com",
- "staemcommunily.ru.com",
- "staemcommuninity.org.ru",
- "staemcommuninty.me",
- "staemcommunitey.com",
- "staemcommunitiy.com",
- "staemcommunitu.com",
- "staemcommunitu.ru",
- "staemcommunity.click",
- "staemcommunity.com.ru",
- "staemcommunity.info",
- "staemcommunity.org",
- "staemcommunity.ru",
- "staemcommunityi.com",
- "staemcommunityu.ru.com",
- "staemcommuniunity.com",
- "staemcommunlty.com",
- "staemcommunlty.fun",
- "staemcommunlty.ru",
- "staemcommunlty.us",
- "staemcommunninty.com",
- "staemcommunnity.club",
- "staemcommunnity.com",
- "staemcommunnity.ru",
- "staemcommunniuty.com",
- "staemcommunnlty.ru",
- "staemcommuntiy.com",
- "staemcommuntiy.ru",
- "staemcommuntly.ru",
- "staemcommunty.com",
- "staemcommunty.ru",
- "staemcommuntyi.ru",
- "staemcommunulty.ru",
- "staemcommunyti.ru.com",
- "staemcommynity.xyz",
- "staemcomnrnunitiy.ru.com",
- "staemcomnuinty.ru",
- "staemcomnumity.ru",
- "staemcomnunity.fun",
- "staemcomnunity.org",
- "staemcomnunlty.ru",
- "staemcomnunyti.club",
- "staemcomnunyti.ru",
- "staemcomnunyti.xyz",
- "staemcomrnunity.ru.com",
- "staemcomrnunity.ru",
- "staemcomrnunity.store",
- "staemcomrrunity.com",
- "staemcomumity.com",
- "staemcomunetys.ru.com",
- "staemcomunitly.xyz",
- "staemcomunity.com",
- "staemcomunity.ru",
- "staemcomunnity.com",
- "staemcomunyti.ru",
- "staemconmuilty.com",
- "staemconmunilty.com",
- "staemconmunity.com",
- "staemconmunity.ru.com",
- "staemconmunity.ru",
- "staemconmunity.xyz",
- "staemconmunlty.ru",
- "staemcoommnunity.ru",
- "staemcoommnuty.ru",
- "staemcoommunity.ru",
- "staemcoommunlty.ru",
- "staemcoommuntiy.ru",
- "staemcoommunty.ru",
- "staemcoomnunlty.ru",
- "staemcoomnunty.ru",
- "staemcoomunity.ru",
- "staemcoomuntiy.ru",
- "staemcoomuunity.ru",
- "staemcoomuunity.xyz",
- "staemcoomuunty.ru",
- "staemcormurnity.com",
- "staemcornmunity.com",
- "staemcornmunity.online",
- "staemcornmunity.ru.com",
- "staemcornmunity.ru",
- "staemcornmunity.xyz",
- "staemcornmuntiy.ru",
- "staemcorrmunity.com",
- "staemcrommuninty.com.profiles-76577258786.ml",
- "staemcrommuninty.com",
- "staemcrommunity.com.profiles-768590190751377476483.me",
- "staemcrornmmunity.com.profiles-75921098086.me",
- "staemcummunity.ru.com",
- "staemcummunlty.com",
- "staemmcommunity.ru",
- "staemncrommunity.store",
- "staempawered.xyz",
- "staemporewed.xyz",
- "staempovered.com",
- "staempowered.space",
- "staempowered.xyz",
- "staermcormmunity.com",
- "staermcrommunity.me",
- "staermcrommunty.me",
- "staermnconnumti.com",
- "staerncoinunitiy.me",
- "staerncormmunity.com",
- "staerncornmunity.co",
- "staerncornmunity.com",
- "staffcups.ru",
- "staffstatsgo.com",
- "stamcomunnity.pp.ua",
- "stamconnunnity.xyz",
- "stammcommunity.com",
- "stammcornunity.xyz",
- "stampowered.com",
- "starmcommunity.net",
- "starrygamble.com",
- "stat-csgo.ru",
- "stats-cs.ru",
- "stayempowered.org",
- "stceamcomminity.com",
- "stcommunity.xyz",
- "ste-trade.ru.com",
- "ste.amcommunity.com",
- "stea-me.ru",
- "stea-sgplay.ru",
- "steaamcammunitiy.com",
- "steaamcamunity.com",
- "steaamcommmunity.com",
- "steaamcommunity.club",
- "steaamcommunnity.co",
- "steaamcommunnity.com",
- "steaamcommunnity.ru.com",
- "steaamcomunity.com",
- "steaamcomunity.net",
- "steaamcomunity.ru.com",
- "steaamconnmunlty.com",
- "steaamcorrrmunity.com",
- "steacmommunity.com",
- "steacommnunity.com",
- "steacommunilty.ru.com",
- "steacommunity.com",
- "steacommunity.net.ru",
- "steacommunity.org.ru",
- "steacommunity.ru.com",
- "steacommunity.site",
- "steacommunnity.com",
- "steacommunty.ru",
- "steacomnmunify.fun",
- "steacomnmunity.com",
- "steacomnunity.ru.com",
- "steaemcamunity.xyz",
- "steaemcommunity.pp.ru",
- "steaemcommunity.ru.com",
- "steaemcomunity.com",
- "steaimcoimmunity.com",
- "steaimcomminnity.ru",
- "steaimcommnunity.com",
- "steaimcommumitiy.com",
- "steaimcommuniity.com",
- "steaimcommunitiy.com",
- "steaimcommunytiu.com",
- "steaimecommintliy.com",
- "steaimecommuninitiy.com",
- "steaimecommunytiu.com",
- "steaimecommunytu.com",
- "steaimeecommunity.com",
- "stealcommuniti.ru",
- "stealcommunity.com",
- "stealcommunlti.com",
- "stealmcommulnitycom.xyz",
- "stealmcommunity.ru",
- "steam-account.ru.com",
- "steam-account.ru",
- "steam-account.site",
- "steam-accounts.com",
- "steam-analyst.ru",
- "steam-announcements1.xyz",
- "steam-auth.com",
- "steam-auth.ru",
- "steam-cammuneti.com",
- "steam-communiity.ru",
- "steam-community.net.ru",
- "steam-community.org.ru",
- "steam-community.ru.com",
- "steam-community.xyz",
- "steam-community1.xyz",
- "steam-communitygifts.xyz",
- "steam-communitygifts1.xyz",
- "steam-communitysource.xyz",
- "steam-communitysource1.xyz",
- "steam-communitytrade.xyz",
- "steam-comunity.me",
- "steam-cs-good.ru",
- "steam-cs.ru",
- "steam-csgo-game.ru",
- "steam-csgo-good.ru",
- "steam-csgo-store.ru",
- "steam-csgo.ru",
- "steam-csgocom.ru",
- "steam-csgogame.ru",
- "steam-csgoplay.ru",
- "steam-discord.com",
- "steam-discord.ru",
- "steam-discords.com",
- "steam-dlscord.com",
- "steam-free-nitro.ru",
- "steam-g5chanaquyufuli.ru",
- "steam-game-csgo.ru",
- "steam-gametrade.xyz",
- "steam-historyoffer.xyz",
- "steam-hometrade.xyz",
- "steam-hometrades.xyz",
- "steam-hype.com",
- "steam-login.ru",
- "steam-login1.xyz",
- "steam-nitro.com",
- "steam-nitro.ru",
- "steam-nitro.store",
- "steam-nitros.com",
- "steam-nitros.ru",
- "steam-nltro.com",
- "steam-nltro.ru",
- "steam-nltros.ru",
- "steam-offer.com",
- "steam-offersgames.xyz",
- "steam-offersofficial.xyz",
- "steam-offerstore.xyz",
- "steam-officialtrade.xyz",
- "steam-play-csgo.ru",
- "steam-povered.xyz",
- "steam-power.xyz",
- "steam-power1.xyz",
- "steam-powered-games.com",
- "steam-powered.xyz",
- "steam-powered1.xyz",
- "steam-poweredexchange.xyz",
- "steam-poweredoffer.xyz",
- "steam-poweredoffers.xyz",
- "steam-poweredtrades.xyz",
- "steam-profile.com",
- "steam-promo-page.ml",
- "steam-rep.com",
- "steam-ru.ru",
- "steam-service.ru",
- "steam-servicedeals.xyz",
- "steam-servicedeals1.xyz",
- "steam-site.ru",
- "steam-sourcecommunity.xyz",
- "steam-sourcecommunity1.xyz",
- "steam-storetrade.xyz",
- "steam-storetrade1.xyz",
- "steam-support.xyz",
- "steam-trade.xyz",
- "steam-tradegame.xyz",
- "steam-tradehome.xyz",
- "steam-tradeoffer.com",
- "steam-tradeoffer.xyz",
- "steam-trades.icu",
- "steam-tradeshome.xyz",
- "steam-tradestore.xyz",
- "steam-tradestore1.xyz",
- "steam.99box.com",
- "steam.cards",
- "steam.cash",
- "steam.cheap",
- "steam.codes",
- "steam.communty.com",
- "steam.communyty.worldhosts.ru",
- "steam.comnunity.com",
- "steam.luancort.com",
- "steam.mmosvc.com",
- "steam4you.online",
- "steamaccount.xyz",
- "steamaccountgenerator.ru.com",
- "steamaccounts.net",
- "steamaccounts.org",
- "steamacommunity.com",
- "steamanalysts.com",
- "steambrowser.xyz",
- "steamc0mmunity.com",
- "steamc0munnity.site",
- "steamcamiutity.com",
- "steamcammiuniltty.com",
- "steamcammmunity.ru",
- "steamcammnuity.com",
- "steamcammuinity.com",
- "steamcammuniety.com",
- "steamcammunitey.com",
- "steamcammuniti.ru",
- "steamcammunitu.com",
- "steamcammunitu.ru.com",
- "steamcammunity-profile.ru",
- "steamcammunity.net",
- "steamcammunity.top",
- "steamcammunlty.ru",
- "steamcammuntiy.com",
- "steamcammunty.com",
- "steamcammunuty.com",
- "steamcammunyty.fun",
- "steamcammunyty.ru",
- "steamcamnunity.com.ru",
- "steamcamnunity.ru",
- "steamcamunite.com",
- "steamcamunitey.com",
- "steamcamunitu.com",
- "steamcamunitu.xyz",
- "steamcamunity-profile.ru",
- "steamcamunity.com",
- "steamcamunity.ru",
- "steamcamunity.top",
- "steamcamunity.xyz",
- "steamcamunlty.com",
- "steamcamunnity.xyz",
- "steamcannunlty.com",
- "steamcard.me",
- "steamccommuniity.com",
- "steamccommunity.com",
- "steamccommunity.net",
- "steamccommunity.ru.com",
- "steamccommunityy.ru",
- "steamccommunyty.ru",
- "steamccommurity.ru",
- "steamccommyunity.com",
- "steamccomunnity.ru.com",
- "steamcconmmuunity.co",
- "steamchinacsgo.ru",
- "steamcmmunuti.ru",
- "steamcmmunyti.ru",
- "steamcmunity.com",
- "steamco.mmunity.com",
- "steamco.ru",
- "steamcoarnmmnunity.ru.com",
- "steamcodesgen.com",
- "steamcokmunity.com",
- "steamcomannlty.xyz",
- "steamcombain.com",
- "steamcomcmunlty.com",
- "steamcomcunity.ru",
- "steamcominity.ru",
- "steamcominuty.ru",
- "steamcomity.com",
- "steamcomiunity.com",
- "steamcomiunity.xyz",
- "steamcomiynuytiy.net.ru",
- "steamcommenitry.ru",
- "steamcommenity.ru",
- "steamcommeunity.com",
- "steamcommhnity.com",
- "steamcomminiity.site",
- "steamcomminiti.ru",
- "steamcomminity.com",
- "steamcomminity.ru.com",
- "steamcomminity.ru",
- "steamcomminnty.com",
- "steamcommintty.com",
- "steamcomminty.ru",
- "steamcomminulty.ru",
- "steamcomminuly.com",
- "steamcomminuly.ru",
- "steamcomminutiiu.ru",
- "steamcomminutiu.ru",
- "steamcomminutiy.ru",
- "steamcomminutty.ru",
- "steamcomminuty-offer.ru.com",
- "steamcomminuty.click",
- "steamcomminuty.com",
- "steamcomminuty.link",
- "steamcomminuty.me",
- "steamcomminuty.nl",
- "steamcomminuty.repl.co",
- "steamcomminuty.ru.com",
- "steamcomminuty.ru",
- "steamcomminuty.xyz",
- "steamcomminyti.ru",
- "steamcomminytiu.com",
- "steamcomminytiu.ru",
- "steamcomminytiy.ru",
- "steamcomminytu.click",
- "steamcomminytu.com",
- "steamcomminytu.link",
- "steamcomminytu.ru",
- "steamcomminyty.ru.com",
- "steamcommiuinity.com",
- "steamcommiunitiy.pp.ru",
- "steamcommiunitty.ru",
- "steamcommiunity.pp.ru",
- "steamcommiunity.ru",
- "steamcommiunniutty.net.ru",
- "steamcommiunty.ru",
- "steamcommiynitiy.net.ru",
- "steamcommllty.com",
- "steamcommlnuty.com",
- "steamcommlunity.com",
- "steamcommmuiniity.ru",
- "steamcommmunitty.site",
- "steamcommmunity.xyz",
- "steamcommmunlity.com",
- "steamcommmunnity.com",
- "steamcommmunty.com",
- "steamcommninty.com",
- "steamcommnity.com.ru",
- "steamcommnity.com",
- "steamcommnity.ru",
- "steamcommnity.store",
- "steamcommnlty.com",
- "steamcommnlty.xyz",
- "steamcommnmunity.ru",
- "steamcommnnity.net.ru",
- "steamcommnnunity.ru",
- "steamcommnnunnity.world",
- "steamcommntiy.xyz",
- "steamcommnuitly.com",
- "steamcommnuitty.com",
- "steamcommnultiy.ru",
- "steamcommnulty.com",
- "steamcommnulty.store",
- "steamcommnunily.com",
- "steamcommnunily.xyz",
- "steamcommnuninty.com",
- "steamcommnuninty.ru.com",
- "steamcommnunitlu.com",
- "steamcommnunitu.com",
- "steamcommnunity.com",
- "steamcommnunity.org.ru",
- "steamcommnunity.ru.com",
- "steamcommnunlty.com",
- "steamcommnunlty.icu",
- "steamcommnunlty.ru",
- "steamcommnunlty.xyz",
- "steamcommnunmity.com",
- "steamcommnunniiy.net.ru",
- "steamcommnuntiy.com",
- "steamcommnunty.ru",
- "steamcommnunylti.com",
- "steamcommnunyti.com",
- "steamcommnunytl.com",
- "steamcommnutly.ru.com",
- "steamcommnutry.com",
- "steamcommnutry.ru",
- "steamcommnuty.site",
- "steamcommnuuntiy.com",
- "steamcommonitey.com",
- "steamcommonnnity.ru.com",
- "steamcommqnity.com",
- "steamcommrnunity.com",
- "steamcommrunitly.com",
- "steamcommrutiny.ru",
- "steamcommtity.com",
- "steamcommuanity.ru.com",
- "steamcommuenity.com",
- "steamcommuhity.ru",
- "steamcommuhuity.com",
- "steamcommuilty.ru",
- "steamcommuinilty.com",
- "steamcommuininty.com",
- "steamcommuinitiycom.ru",
- "steamcommuinity.com",
- "steamcommuinity.ru",
- "steamcommuinty.com.ru",
- "steamcommuinuity.com",
- "steamcommuiti.ru",
- "steamcommuitliy.com",
- "steamcommuitly.ru",
- "steamcommuity.com",
- "steamcommuity.ru",
- "steamcommulity.ru",
- "steamcommulltty.com",
- "steamcommullty.ru",
- "steamcommulnity.com",
- "steamcommulnt.ru.com",
- "steamcommulnty.ru",
- "steamcommulty.ru",
- "steamcommumilty.com",
- "steamcommumitiy.com",
- "steamcommumituy.com",
- "steamcommumity.biz",
- "steamcommumity.net",
- "steamcommumiuty.com",
- "steamcommumlity.com",
- "steamcommumnity.com",
- "steamcommumtiy.com",
- "steamcommun1ty.ru",
- "steamcommunely.ru",
- "steamcommuneteiy.com",
- "steamcommunetiy.com",
- "steamcommunetiy.ru",
- "steamcommunetiyi.com",
- "steamcommunetiyy.xyz",
- "steamcommunetu.com",
- "steamcommunety.com",
- "steamcommunety.net.ru",
- "steamcommunety.online",
- "steamcommunety.org.ru",
- "steamcommunety.ru",
- "steamcommunety1i.com",
- "steamcommunetyei.com",
- "steamcommuneuity.ru",
- "steamcommunhity.com",
- "steamcommuni.com",
- "steamcommunicty.com",
- "steamcommunicty.ru.com",
- "steamcommunidy.com",
- "steamcommunieityi.com",
- "steamcommunieti.ru",
- "steamcommunietiy.com",
- "steamcommuniety.com",
- "steamcommuniety.ru",
- "steamcommunifly.ru.com",
- "steamcommunify.com",
- "steamcommunify.ru",
- "steamcommunihty.com",
- "steamcommuniiity.com",
- "steamcommuniilty.ru",
- "steamcommuniitu.site",
- "steamcommuniity.com.ru",
- "steamcommuniiy.online",
- "steamcommuniiy.ru",
- "steamcommunikkty.net.ru",
- "steamcommunili.xyz",
- "steamcommunility.com",
- "steamcommunillty.com",
- "steamcommunillty.net.ru",
- "steamcommunillty.ru.com",
- "steamcommunillty.ru",
- "steamcommunilly.com",
- "steamcommuniltily.ru.com",
- "steamcommuniltiy.online",
- "steamcommuniltiy.ru",
- "steamcommuniltly.com",
- "steamcommunilty.buzz",
- "steamcommunilty.it",
- "steamcommunilty.ru.com",
- "steamcommunilty.us",
- "steamcommunilty.xyz",
- "steamcommuniltys.com",
- "steamcommunilv.com",
- "steamcommunily.buzz",
- "steamcommunily.org",
- "steamcommunily.uno",
- "steamcommunimty.ru.com",
- "steamcommuninity.ru.com",
- "steamcommuninthy.com",
- "steamcommuninty.ru.com",
- "steamcommuninunty.com",
- "steamcommunirtly.ru.com",
- "steamcommunirty.com",
- "steamcommunirty.ru.com",
- "steamcommuniry.com",
- "steamcommuniry.net.ru",
- "steamcommuniry.ru",
- "steamcommunit.org.ru",
- "steamcommunit.ru.com",
- "steamcommunit.ru",
- "steamcommunitcy.ru.com",
- "steamcommunite.com",
- "steamcommunite.ru",
- "steamcommunitey.com",
- "steamcommunitey.ru",
- "steamcommuniteypowered.com",
- "steamcommunitfy.com",
- "steamcommunitfy.ru.com",
- "steamcommunithy.com",
- "steamcommuniti.com.ru",
- "steamcommuniti.org.ru",
- "steamcommuniti.org",
- "steamcommuniti.ru.com",
- "steamcommunitie.net",
- "steamcommunitie.ru.com",
- "steamcommunitie.ru",
- "steamcommunitie.site",
- "steamcommunities.biz",
- "steamcommunitii.xyz",
- "steamcommunitily.com",
- "steamcommunitity.com",
- "steamcommunitiu.ru",
- "steamcommunitiv.com",
- "steamcommunitiy.ru",
- "steamcommunitiycom.ru",
- "steamcommunitiyu.com",
- "steamcommunitiyy.com",
- "steamcommunitj.buzz",
- "steamcommunitl.com",
- "steamcommunitl.net.ru",
- "steamcommunitli.ru",
- "steamcommunitlil.ru",
- "steamcommunitliy.ru.com",
- "steamcommunitlly.com",
- "steamcommunitlly.net",
- "steamcommunitlly.ru.com",
- "steamcommunitlu.com",
- "steamcommunitluy.com",
- "steamcommunitly.com",
- "steamcommunitly.me",
- "steamcommunitmy.ru.com",
- "steamcommunitry.com",
- "steamcommunitry.ru",
- "steamcommunitte.com",
- "steamcommunitte.ru",
- "steamcommunittey.com",
- "steamcommunittrade.xyz",
- "steamcommunittru.co",
- "steamcommunittry.xyz",
- "steamcommunitty.com.ru",
- "steamcommunitty.esplay.eu",
- "steamcommunitty.net",
- "steamcommunitty.site",
- "steamcommunitty.top",
- "steamcommunitu.com-profile-poka.biz",
- "steamcommunitu.com-profiles-mellenouz.trade",
- "steamcommunitu.icu",
- "steamcommunitu.net",
- "steamcommunitu.ru.com",
- "steamcommunitv.ru",
- "steamcommunitvs.com",
- "steamcommunitx.ru.com",
- "steamcommunity-com.xyz",
- "steamcommunity-comtradeoffer.ru",
- "steamcommunity-gifts.xyz",
- "steamcommunity-gifts1.xyz",
- "steamcommunity-nitro.ru",
- "steamcommunity-nitrogeneral.ru",
- "steamcommunity-profile.net",
- "steamcommunity-profiles.ru.com",
- "steamcommunity-source.xyz",
- "steamcommunity-source1.xyz",
- "steamcommunity-trade.xyz",
- "steamcommunity-tradeoffer.com",
- "steamcommunity-tradeoffer.ru.com",
- "steamcommunity-tradeoffer4510426522.ru",
- "steamcommunity-tradeoffers.com",
- "steamcommunity-user.me",
- "steamcommunity-xpubg.xyz",
- "steamcommunity.at",
- "steamcommunity.best",
- "steamcommunity.biz",
- "steamcommunity.ca",
- "steamcommunity.click",
- "steamcommunity.cloud",
- "steamcommunity.cn",
- "steamcommunity.co.ua",
- "steamcommunity.com-id-k4tushatwitchbabydota.ru",
- "steamcommunity.com.ru",
- "steamcommunity.comlappl251490lrust.ru",
- "steamcommunity.de",
- "steamcommunity.digital",
- "steamcommunity.eu",
- "steamcommunity.in",
- "steamcommunity.link",
- "steamcommunity.live",
- "steamcommunity.llc",
- "steamcommunity.mobi",
- "steamcommunity.moscow",
- "steamcommunity.net.in",
- "steamcommunity.pl",
- "steamcommunity.pp.ru",
- "steamcommunity.rest",
- "steamcommunity.ru.net",
- "steamcommunity.ru",
- "steamcommunity.site",
- "steamcommunity.steams.ga",
- "steamcommunity.support",
- "steamcommunity.team",
- "steamcommunity.trade",
- "steamcommunity.us",
- "steamcommunity1.com",
- "steamcommunitya.com",
- "steamcommunityc.com",
- "steamcommunityc.ru",
- "steamcommunitycom.ru.com",
- "steamcommunitycomoffernewpartner989791155tokenjbhldtj6.trade",
- "steamcommunitycomtradeoffer.ru.com",
- "steamcommunitygames.com",
- "steamcommunitygifts.xyz",
- "steamcommunitygifts1.xyz",
- "steamcommunityi.com",
- "steamcommunityi.ru.com",
- "steamcommunityi.ru",
- "steamcommunityid.ru",
- "steamcommunitylink.xyz",
- "steamcommunitym.com",
- "steamcommunitym.ru",
- "steamcommunitynow.com",
- "steamcommunityo.com",
- "steamcommunityoff.com",
- "steamcommunityoffers.org",
- "steamcommunitypubg.com",
- "steamcommunityr.com.ru",
- "steamcommunityru.tk",
- "steamcommunityshop.com",
- "steamcommunitysource.xyz",
- "steamcommunitysource1.xyz",
- "steamcommunitytradeofer.com",
- "steamcommunitytradeoffer.com",
- "steamcommunitytradeoffer.ru",
- "steamcommunitytradeoffter.com",
- "steamcommunitytradeofter.com",
- "steamcommunitytredeoffer.com",
- "steamcommunityu.com",
- "steamcommunityu.ru",
- "steamcommunityw.com",
- "steamcommunityw.net.ru",
- "steamcommunityw.org.ru",
- "steamcommunitywork.com",
- "steamcommunitywork.ml",
- "steamcommunityx.com",
- "steamcommunityy.online",
- "steamcommunityy.ru",
- "steamcommunityz.com",
- "steamcommunityzbn.top",
- "steamcommunityzbo.top",
- "steamcommunityzbq.top",
- "steamcommunityzbr.top",
- "steamcommunityzcd.top",
- "steamcommunityzce.top",
- "steamcommunityzci.top",
- "steamcommunityzda.top",
- "steamcommunityzdb.top",
- "steamcommunityzdd.top",
- "steamcommunityzdl.top",
- "steamcommunityzdp.top",
- "steamcommunityzdq.top",
- "steamcommunityzdr.top",
- "steamcommunityzds.top",
- "steamcommunityzdt.top",
- "steamcommuniuity.com",
- "steamcommuniutiiy.com",
- "steamcommuniutiy.ru",
- "steamcommuniuty.ru",
- "steamcommuniy.com",
- "steamcommuniyt.com",
- "steamcommuniytu.com",
- "steamcommuniyty.ru",
- "steamcommunjti.com",
- "steamcommunjtv.xyz",
- "steamcommunjty.net",
- "steamcommunjty.ru",
- "steamcommunlilty.ru.com",
- "steamcommunlite.com",
- "steamcommunlitily.ru.com",
- "steamcommunlitly.ru",
- "steamcommunlitty.ru.com",
- "steamcommunlitty.ru",
- "steamcommunlity.net",
- "steamcommunlity.ru.com",
- "steamcommunlity.ru",
- "steamcommunlityl.ru",
- "steamcommunliu.com",
- "steamcommunlky.net.ru",
- "steamcommunllity.ru.com",
- "steamcommunllty.com",
- "steamcommunllty.ru",
- "steamcommunlte.ru",
- "steamcommunltiy.club",
- "steamcommunltiy.com",
- "steamcommunltty.com",
- "steamcommunltu.com",
- "steamcommunltuy.com",
- "steamcommunltv.buzz",
- "steamcommunlty-proflle.com.ru",
- "steamcommunlty.biz",
- "steamcommunlty.business",
- "steamcommunlty.cloud",
- "steamcommunlty.company",
- "steamcommunlty.info",
- "steamcommunlty.link",
- "steamcommunlty.pro",
- "steamcommunlty.shop",
- "steamcommunlty.site",
- "steamcommunlty.store",
- "steamcommunlty.top",
- "steamcommunltyu.ru",
- "steamcommunltyy.com",
- "steamcommunly.com",
- "steamcommunly.net.ru",
- "steamcommunmity.com.ru",
- "steamcommunniittly.ru",
- "steamcommunniitty.com",
- "steamcommunniity.com",
- "steamcommunniity.net",
- "steamcommunniity.ru",
- "steamcommunnilty.com",
- "steamcommunnilty.ru",
- "steamcommunnitey.com",
- "steamcommunnitlly.ru",
- "steamcommunnitty.ru",
- "steamcommunnity.co",
- "steamcommunnity.com.ru",
- "steamcommunnity.ml",
- "steamcommunnity.net",
- "steamcommunnity.ru.com",
- "steamcommunnity.ru",
- "steamcommunnjty.com",
- "steamcommunnlity.ru",
- "steamcommunnlty.com.ru",
- "steamcommunnty.ru",
- "steamcommunnuty.ru",
- "steamcommunrinty.ru.com",
- "steamcommunrity.com",
- "steamcommunrity.ru.com",
- "steamcommunrlity.com",
- "steamcommunrrity.com",
- "steamcommunti.com",
- "steamcommuntily.ru.com",
- "steamcommuntily.ru",
- "steamcommuntity.com",
- "steamcommuntity.ru.com",
- "steamcommuntiv.com",
- "steamcommuntiy.com",
- "steamcommuntli.ru",
- "steamcommuntliy.ru",
- "steamcommuntly.com",
- "steamcommuntry.com",
- "steamcommunty.buzz",
- "steamcommunty.com.ru",
- "steamcommunty.com",
- "steamcommunty.net",
- "steamcommunty.pw",
- "steamcommunty.ru.com",
- "steamcommuntyy.ru",
- "steamcommunuaity.xyz",
- "steamcommunuety.ru",
- "steamcommunuity.net",
- "steamcommunuity.ru",
- "steamcommununty-con.ru",
- "steamcommununty.ru",
- "steamcommunury.ru",
- "steamcommunute.com",
- "steamcommunuti.co",
- "steamcommunuti.ru",
- "steamcommunutii.ru",
- "steamcommunutiy.com",
- "steamcommunutry.com",
- "steamcommunutry.ru",
- "steamcommunutty.com",
- "steamcommunutty.ru",
- "steamcommunutuy.com",
- "steamcommunuty.buzz",
- "steamcommunuty.co",
- "steamcommunuty.link",
- "steamcommunuty.org.ru",
- "steamcommunuty.ru",
- "steamcommunutyu.com",
- "steamcommunvti.ru",
- "steamcommunyity.ru",
- "steamcommunylty.ru",
- "steamcommunyte.com",
- "steamcommunyti.com",
- "steamcommunyti.info",
- "steamcommunytitradeoffer.com",
- "steamcommunytiu.com",
- "steamcommunytiu.ru",
- "steamcommunytiy.ru",
- "steamcommunytiy.tk",
- "steamcommunytu.ru",
- "steamcommunyty.com",
- "steamcommunyty.ru.com",
- "steamcommunyty.xyz",
- "steamcommunytytradeofferphobos.ru",
- "steamcommuriity.com",
- "steamcommurity.ru",
- "steamcommurjty.com",
- "steamcommurlity.com",
- "steamcommurlty.com",
- "steamcommurnity.com",
- "steamcommurnuity.com",
- "steamcommutinny.ru.com",
- "steamcommutiny.com",
- "steamcommutiny.ru.com",
- "steamcommutiny.ru",
- "steamcommutiny.xyz",
- "steamcommutry.ru",
- "steamcommuty.com",
- "steamcommutyniu.com",
- "steamcommutyniy.com",
- "steamcommuuity.net.ru",
- "steamcommuulty.com",
- "steamcommuunitey.com",
- "steamcommuunitty.ru.com",
- "steamcommuunity.net.ru",
- "steamcommuunity.pp.ru",
- "steamcommuunity.ru.com",
- "steamcommuunity.ru",
- "steamcommuunjty.com",
- "steamcommuunlity.com",
- "steamcommuunlty.com",
- "steamcommuwunity.com",
- "steamcommuynity.ru.com",
- "steamcommyinuty.ru",
- "steamcommymity.ru",
- "steamcommynite.com",
- "steamcommyniti.ru",
- "steamcommyniti.xyz",
- "steamcommynitiu.com",
- "steamcommynitry.ru",
- "steamcommynitu.com",
- "steamcommynitu.net.ru",
- "steamcommynitu.ru.com",
- "steamcommynitu.ru",
- "steamcommynitu.xyz",
- "steamcommynituy.com",
- "steamcommynity.icu",
- "steamcommynity.ru",
- "steamcommynity.space",
- "steamcommynityprofile.ru",
- "steamcommynltu.com",
- "steamcommynlty.com",
- "steamcommynlty.ru",
- "steamcommynnityy.com",
- "steamcommynuti.ru",
- "steamcommynutiy.ru",
- "steamcommynutu.ru",
- "steamcommynuty.ru.com",
- "steamcommynyti.ru",
- "steamcommynyti.site",
- "steamcommytiny.com",
- "steamcommytuniu.com",
- "steamcommyuinity.net.ru",
- "steamcommyunity.com",
- "steamcomnenity.ru.com",
- "steamcomninuty.ru.com",
- "steamcomninytiu.com",
- "steamcomniunity.com",
- "steamcomnmnuty.ru",
- "steamcomnmrunity.online",
- "steamcomnmrunity.ru",
- "steamcomnmufly.ru.com",
- "steamcomnmuituy.com",
- "steamcomnmuity.ru",
- "steamcomnmunity.com.ru",
- "steamcomnmunlty.com",
- "steamcomnmuntiy.ru.com",
- "steamcomnmutly.ru.com",
- "steamcomnmuunity.ru.com",
- "steamcomnmynitu.com",
- "steamcomnnity.net.ru",
- "steamcomnnlty.com",
- "steamcomnnuity.com",
- "steamcomnnunilty.com",
- "steamcomnnunity.co",
- "steamcomnnunity.ru.com",
- "steamcomnnunity.ru",
- "steamcomnnunlty.ru",
- "steamcomnnunty.ru",
- "steamcomnnuty.ru",
- "steamcomnnynlty.com",
- "steamcomnuenuity.com",
- "steamcomnuhity.com",
- "steamcomnuiti.xyz",
- "steamcomnulty.com",
- "steamcomnumilty.com",
- "steamcomnumily.com",
- "steamcomnumity.com",
- "steamcomnumity.org.ru",
- "steamcomnumity.ru.com",
- "steamcomnumity.ru",
- "steamcomnumity.xyz",
- "steamcomnumlity.com",
- "steamcomnumlty.com",
- "steamcomnumlty.ru",
- "steamcomnumnity.com",
- "steamcomnumty.ru",
- "steamcomnuniity.com.ru",
- "steamcomnuniity.pp.ru",
- "steamcomnuniity.ru.com",
- "steamcomnunilty.com",
- "steamcomnunilty.ru.com",
- "steamcomnunily.co",
- "steamcomnunirty.ru",
- "steamcomnuniti.com",
- "steamcomnunitiy.com",
- "steamcomnunitiy.ru",
- "steamcomnunitly.com",
- "steamcomnunitly.tk",
- "steamcomnunitry.ru",
- "steamcomnunitty.com",
- "steamcomnunity.com",
- "steamcomnunity.net",
- "steamcomnunity.org.ru",
- "steamcomnunity.ru",
- "steamcomnunity.site",
- "steamcomnunityprofile.ru.com",
- "steamcomnunlity.com",
- "steamcomnunlity.ru",
- "steamcomnunllty.com",
- "steamcomnunllty.net",
- "steamcomnunlty.ru.com",
- "steamcomnunlty.ru",
- "steamcomnunluty.ru",
- "steamcomnunmity.com",
- "steamcomnunnirty.ru",
- "steamcomnunniry.ru",
- "steamcomnunnity.com",
- "steamcomnunnity.net.ru",
- "steamcomnunnity.net",
- "steamcomnunnlty.ru",
- "steamcomnuntiy.com",
- "steamcomnuntty.ru.com",
- "steamcomnunutiy.ru",
- "steamcomnunuty.com",
- "steamcomnunuty.ru",
- "steamcomnunytu.ru",
- "steamcomnurity.com",
- "steamcomnurity.xyz",
- "steamcomnutiny.online",
- "steamcomnutiny.ru.com",
- "steamcomnutiny.ru",
- "steamcomnuty.com",
- "steamcomnuunlty.com",
- "steamcomnynlity.ru",
- "steamcomonity.com",
- "steamcomrmunity.ru.com",
- "steamcomrmunnuity.ru.com",
- "steamcomrneuneity.com",
- "steamcomrninuty.link",
- "steamcomrninuty.ru",
- "steamcomrninuty.site",
- "steamcomrnity.xyz",
- "steamcomrnlnuty.site",
- "steamcomrnumity.com",
- "steamcomrnunite.com",
- "steamcomrnuniti.ru.com",
- "steamcomrnunitu.ru.com",
- "steamcomrnunitu.ru",
- "steamcomrnunity.com.ru",
- "steamcomrnunity.online",
- "steamcomrnunity.ru.com",
- "steamcomrnunity.ru",
- "steamcomrnunity.site",
- "steamcomrnunity.su",
- "steamcomrnunity.xyz",
- "steamcomrnunlty.com",
- "steamcomrnunlty.ru",
- "steamcomrnunuity.ru.com",
- "steamcomrnyniti.ru.com",
- "steamcomrnyniti.ru",
- "steamcomrrnunity.com",
- "steamcomrrnunity.net.ru",
- "steamcomrrnunity.ru",
- "steamcomrunily.com",
- "steamcomrunity.com",
- "steamcomueniity.ru",
- "steamcomumity.com",
- "steamcomumunty.com",
- "steamcomunety.com",
- "steamcomunety.ru",
- "steamcomuniety.ru",
- "steamcomuniiity.com",
- "steamcomuniitly.ru.com",
- "steamcomuniity.ru.com",
- "steamcomunillty.ru.com",
- "steamcomuniltu.xyz",
- "steamcomunilty.com",
- "steamcomunily.ru.com",
- "steamcomuninruty.ru",
- "steamcomuniti.com",
- "steamcomuniti.ru",
- "steamcomuniti.xyz",
- "steamcomunitly.pp.ru",
- "steamcomunitly.ru",
- "steamcomunitty.ru.com",
- "steamcomunitu.com",
- "steamcomunitu.net.ru",
- "steamcomunitu.ru",
- "steamcomunituy.com",
- "steamcomunity-comid12121212123244465.ru",
- "steamcomunity-nitro-free.ru",
- "steamcomunity.com.ru",
- "steamcomunity.com",
- "steamcomunity.me",
- "steamcomunity.net.ru",
- "steamcomunity.org.ru",
- "steamcomunity.ru",
- "steamcomunity.us",
- "steamcomunityo.com",
- "steamcomunitytrades.xyz",
- "steamcomunityy.com",
- "steamcomunlitly.ru.com",
- "steamcomunlty.ru.com",
- "steamcomunmity.ru.com",
- "steamcomunniity.ru",
- "steamcomunninuty.com",
- "steamcomunnitly.ru.com",
- "steamcomunnitu.xyz",
- "steamcomunnity.fun",
- "steamcomunnity.ru.com",
- "steamcomunnity.site",
- "steamcomunnity.xyz",
- "steamcomunnlty.com",
- "steamcomunnuity.com",
- "steamcomunnuty.com",
- "steamcomunnyti.ru",
- "steamcomuntty.com",
- "steamcomunty.org.ru",
- "steamcomunuty.com",
- "steamcomunuty.ru",
- "steamcomunyiti.ru.com",
- "steamcomunyti.com",
- "steamcomunytiu.com",
- "steamcomuuniity.com",
- "steamcomuunity.com",
- "steamcomuunity.ru.com",
- "steamcomyniti.xyz",
- "steamcomynitu.ru",
- "steamcomynity.ru",
- "steamcomynlty.com",
- "steamcomynnitu.net.ru",
- "steamconimmunity.com",
- "steamconminuty.ru",
- "steamconmiunity.ru",
- "steamconmmuntiy.com",
- "steamconmnmnunity.ru",
- "steamconmnmunity.ru",
- "steamconmnunitiy.ru.com",
- "steamconmnunitiy.ru",
- "steamconmnunity.co",
- "steamconmnunity.com",
- "steamconmnunity.ru",
- "steamconmnunuty.ru.com",
- "steamconmnutiny.ru",
- "steamconmuhlty.com",
- "steamconmumity.com.ru",
- "steamconmumity.com",
- "steamconmumity.ru.com",
- "steamconmumity.ru",
- "steamconmumltu.com.ru",
- "steamconmummity.ru",
- "steamconmumnity.com",
- "steamconmuniti.ru",
- "steamconmunitly.com",
- "steamconmunitty.com",
- "steamconmunity.co",
- "steamconmunity.com.ru",
- "steamconmunity.pp.ru",
- "steamconmunity.xyz",
- "steamconmunjty.com",
- "steamconmunlly.com",
- "steamconmunlty.com.ru",
- "steamconmunlty.com",
- "steamconmunlty.ru",
- "steamconmunnitry.ru",
- "steamconmunnlty.ru",
- "steamconmunuty.ru",
- "steamconmunyty.com",
- "steamconmunyty.ru",
- "steamconnmuhity.com",
- "steamconnmunitu.net.ru",
- "steamconnmunity.ru",
- "steamconnmunlty.com",
- "steamconnmunlty.ru.com",
- "steamconnmunlty.ru",
- "steamconnnnunity.net.ru",
- "steamconnnnunity.org.ru",
- "steamconnumity.ru.com",
- "steamconnummity.ru",
- "steamconnumuty.com",
- "steamconnuniitty.tk",
- "steamconnunirty.ru",
- "steamconnunitiy.com",
- "steamconnunity.com.ru",
- "steamconnunity.com",
- "steamconnunity.de",
- "steamconnunity.fun",
- "steamconnunity.net",
- "steamconnunity.pp.ru",
- "steamconnunity.ru.com",
- "steamconnunlty.com",
- "steamconummity.ru",
- "steamconunity.cf",
- "steamconunity.ru",
- "steamconunity.tk",
- "steamconunlty.ru",
- "steamconynuyty.net.ru",
- "steamconynuyty.org.ru",
- "steamcoominuty.site",
- "steamcoomminuty.site",
- "steamcoommunety.com",
- "steamcoommuniity.link",
- "steamcoommuniity.ru",
- "steamcoommunilty.com",
- "steamcoommunity.pp.ru",
- "steamcoommunity.ru.com",
- "steamcoommunllty.com",
- "steamcoommunlty.ru",
- "steamcoommunuity.com",
- "steamcoommunuty.com",
- "steamcoomrnmunity.ml",
- "steamcoomunity-nitro.site",
- "steamcoomunitye.com",
- "steamcoomunjty.com",
- "steamcoomunlty.com",
- "steamcoomunlty.net",
- "steamcoomunlty.ru",
- "steamcoomunnity.com",
- "steamcoomunnity.ru",
- "steamcoomynity.ru",
- "steamcoonmuntiy.ru",
- "steamcoormmunity.com",
- "steamcormmmunity.com",
- "steamcormmunity.com",
- "steamcormmunity.net.ru",
- "steamcormmunity.ru.com",
- "steamcormmuntiy.com",
- "steamcormmuuity.ru",
- "steamcormrunity.com",
- "steamcormunity.ru",
- "steamcormunity.xyz",
- "steamcormurnity.com",
- "steamcornminity.ru.com",
- "steamcornminty.xyz",
- "steamcornminuty.com",
- "steamcornmmunity.com",
- "steamcornmnitu.ru.com",
- "steamcornmnuity.com",
- "steamcornmunety.com",
- "steamcornmunify.ru.com",
- "steamcornmuniity.net.ru",
- "steamcornmunily.ru",
- "steamcornmunit.ru.com",
- "steamcornmunite.com",
- "steamcornmunity.fun",
- "steamcornmunity.net.ru",
- "steamcornmunity.org",
- "steamcornmunty.com",
- "steamcornmunyti.ru",
- "steamcornmynitu.ru",
- "steamcornmynity.ru",
- "steamcornrnuity.com",
- "steamcornrnunity.com.ru",
- "steamcornrnunity.fun",
- "steamcornrrnunity.com",
- "steamcorrmunity.com",
- "steamcorrnmunity.ru",
- "steamcorrnunity.org",
- "steamcoummunitiy.com",
- "steamcoummunity.com",
- "steamcrommunlty.me",
- "steamcromnmunity-com.profiles-7685981598976.me",
- "steamcronnmmuniry.me",
- "steamcsgo-game.ru",
- "steamcsgo-play.ru",
- "steamcsgo.ru",
- "steamcsgoplay.ru",
- "steamcummunity.com.ru",
- "steamcummunity.com",
- "steamcummunity.ru.com",
- "steamcummunity.ru",
- "steamcummunityy.pp.ua",
- "steamcummunnity.com",
- "steamcumumunity.com.ru",
- "steamdesksupport.com",
- "steamdiscord.com",
- "steamdiscord.ru",
- "steamdiscordi.com",
- "steamdiscordj.com",
- "steamdiscords.com",
- "steamdiscrod.ru",
- "steamdlscord.com",
- "steamdlscords.com",
- "steamdocs.xyz",
- "steamdomain.online",
- "steamdomain.ru",
- "steamdommunity.com",
- "steamecommuinty.com",
- "steamecommunitiiy.com",
- "steamecommunitiy.com",
- "steamecommunituiy.com",
- "steamecommunity.net",
- "steamecommunity.org",
- "steamecommunity.pp.ua",
- "steamecommunity.ru.com",
- "steamecommuniuty.com",
- "steamecommunlty.com.ru",
- "steamecommunlty.com",
- "steamecommunytu.com",
- "steamecomunity.com.ru",
- "steamedpowered.com",
- "steamepowered.com",
- "steamescommunity.com",
- "steamgame-csgo.ru",
- "steamgame-trade.xyz",
- "steamgame.net.ru",
- "steamgamepowered.net",
- "steamgames.net.ru",
- "steamgamesroll.ru",
- "steamgametrade.xyz",
- "steamgiftcards.cf",
- "steamgifts.net.ru",
- "steamgiveaway.cc",
- "steamgiveawayfree.ru",
- "steamgivenitro.com",
- "steamglft.ru",
- "steamguard.ir",
- "steamhelp.net",
- "steamhome-trade.xyz",
- "steamhome-trades.xyz",
- "steamhometrade.xyz",
- "steamhometrades.xyz",
- "steamicommunnity.com",
- "steamid.ru",
- "steamitem.xyz",
- "steamkey.ru",
- "steamkommunity.net.ru",
- "steamkommunity.org.ru",
- "steamlcommunity.net.ru",
- "steamlcommunity.org.ru",
- "steamlcommunity.ru.com",
- "steamm.store",
- "steammatily.online",
- "steammatily.ru",
- "steammcamunitu.com",
- "steammcamunity.com",
- "steammcamunity.ru.com",
- "steammcomminity.ru",
- "steammcomminuty.ru",
- "steammcommmunlty.pp.ua",
- "steammcommunety.com",
- "steammcommuniity.ru",
- "steammcommunily.net.ru",
- "steammcommunitey.com",
- "steammcommunitly.ru",
- "steammcommunity-trade.xyz",
- "steammcommunity.com",
- "steammcommunity.ru.com",
- "steammcommunity.ru",
- "steammcommunnity.ru",
- "steammcommunyti.ru",
- "steammcommuunityy.ru.com",
- "steammcomtradeoff.com",
- "steammcomunit.ru",
- "steammcomunity.ru",
- "steammcomunlty.ru",
- "steammcomunnity.com",
- "steammcounity.ru.com",
- "steammecommunity.com",
- "steammncommunty.ru.com",
- "steamncommnunity.ru",
- "steamncommnunty.ru",
- "steamncommuinity.com",
- "steamncommumity.ru",
- "steamncommuniity.com",
- "steamncommunitiy.com",
- "steamncommunitu.co",
- "steamncommunity.com",
- "steamncommunity.pp.ru",
- "steamncommunity.ru",
- "steamncommunity.xyz",
- "steamncommunytu.ru",
- "steamncomnunlty.com.ru",
- "steamncomunitity.com",
- "steamncomunity.com",
- "steamncomunity.xyz",
- "steamnconmunity.com",
- "steamnconmunity.ru.com",
- "steamnconmunity.work",
- "steamnconnmunity.com",
- "steamnitro.com",
- "steamnitrol.com",
- "steamnitros.com",
- "steamnitros.ru",
- "steamnitrro.com",
- "steamnltro.com",
- "steamnltros.com",
- "steamnltros.ru",
- "steamnmcomunnity.co",
- "steamocmmunity.me",
- "steamoemmunity.com",
- "steamoffer-store.xyz",
- "steamoffered.trade",
- "steamoffergames.xyz",
- "steamommunity.com",
- "steamoowered.com",
- "steamowered.com",
- "steampawared.club",
- "steampawered.store",
- "steampcwered.com",
- "steampewared.com",
- "steampewered.com",
- "steampiwered.com",
- "steampoeer.com",
- "steampoeerd.com",
- "steampoewred.com",
- "steampoiwered.com",
- "steampoowered.com",
- "steampowaered.com",
- "steampoward.com",
- "steampowder.com",
- "steampowed.com",
- "steampoweded.com",
- "steampoweeed.com",
- "steampowened.ru.com",
- "steampower.co",
- "steampower.de",
- "steampower.space",
- "steampowerco.com",
- "steampowerd.com",
- "steampowerd.net",
- "steampowerde.com",
- "steampowerded.com",
- "steampowerdwallet.com",
- "steampowere.com",
- "steampoweread.com",
- "steampowerec.com",
- "steampowered-offer.xyz",
- "steampowered-offers.xyz",
- "steampowered-swap.xyz",
- "steampowered-swap1.xyz",
- "steampowered-trades.xyz",
- "steampowered.company",
- "steampowered.de",
- "steampowered.freeskins.ru.com",
- "steampowered.help",
- "steampowered.irl.com.pk",
- "steampowered.jcharante.com",
- "steampowered.org",
- "steampowered.tw",
- "steampowered.us",
- "steampowered.xyz",
- "steampoweredcinema.com",
- "steampoweredcommunity.com",
- "steampoweredexchange.xyz",
- "steampoweredexchanges.xyz",
- "steampoweredkey.com",
- "steampoweredmarketing.com",
- "steampoweredoffer.xyz",
- "steampoweredoffers.xyz",
- "steampoweredpoetry.com",
- "steampoweredshow.com",
- "steampoweredswap.xyz",
- "steampoweredtrades.xyz",
- "steampowereed.com",
- "steampowererd.com",
- "steampowerered.com",
- "steampowerewd.com",
- "steampowerred.com",
- "steampowers.com",
- "steampowers.org",
- "steampowerwd.com",
- "steampowerwed.com",
- "steampowoereid.com",
- "steampowored.com",
- "steampowrd.com",
- "steampowred.ru",
- "steampowwered.com",
- "steampowwred.com",
- "steamppwrred.com",
- "steampromo.net.ru",
- "steamproxy.net",
- "steampunch-twitch.co",
- "steampwered.com",
- "steampwoered.com",
- "steamrccommunity.com",
- "steamrcommuniity.com",
- "steamrcommunity.ru",
- "steamroll.org.ru",
- "steamrolll.net.ru",
- "steamrolls.net.ru",
- "steamrolls.pp.ru",
- "steamrommunily.com",
- "steamrommunity.org.ru",
- "steamru.org",
- "steams-community.ru",
- "steams-discord.ru",
- "steamscommmunity.com",
- "steamscommunitey.com",
- "steamscommunity.com",
- "steamscommunity.pro",
- "steamscommunity.ru",
- "steamscommunyti.com",
- "steamscommynitu.co",
- "steamscomnunity.com",
- "steamscomnunyti.com",
- "steamsconmunity.com",
- "steamsdiscord.com",
- "steamservice-deals.xyz",
- "steamservice-deals1.xyz",
- "steamservicedeals.xyz",
- "steamservicedeals1.xyz",
- "steamshensu.top",
- "steamskincs.ru",
- "steamsnitro.ru",
- "steamsoftware.info",
- "steamsommunity.com",
- "steamsommunity.ru",
- "steamsomunity.com",
- "steamsourcecommunity.xyz",
- "steamsourcecommunity1.xyz",
- "steamstore.map2.ssl.hwcdn.net",
- "steamstore.site",
- "steamstorecsgo.com",
- "steamstorepowered.com",
- "steamstoretrade1.xyz",
- "steamstradecommunity.xyz",
- "steamsupportpowered.icu",
- "steamswap.xyz",
- "steamtrade-game.xyz",
- "steamtrade-home.xyz",
- "steamtrade-store.xyz",
- "steamtrade-store1.xyz",
- "steamtradecommunity.fun",
- "steamtradehome.xyz",
- "steamtradeoffeer.com",
- "steamtradeoffer.net",
- "steamtradeprofile.com",
- "steamtrades-home.xyz",
- "steamtrades-store.xyz",
- "steamtrades.com",
- "steamtradeshome.xyz",
- "steamtradesofer.com",
- "steamtradestore.xyz",
- "steamtradestore1.xyz",
- "steamunlocked.online",
- "steamunlocked.pro",
- "steamunpowered.com",
- "steamuppowered.com",
- "steamuserimages-a.akamaid.net",
- "steamwalletbd.com",
- "steamwalletcodes.net",
- "steamwanmeics.ru",
- "steamwcommunity.com",
- "steamwcommunity.net",
- "steamworkspace.com",
- "steamzcommunity.com",
- "steanammunuty.ml",
- "steancammunity.com",
- "steancammunity.ru",
- "steancammunlte.com",
- "steancammunlty.com",
- "steancammunyti.com",
- "steanccommunity.ru",
- "steancimnunity.ru",
- "steancommanty.ru.com",
- "steancommeuniliy.ru.com",
- "steancomminity.com",
- "steancomminity.ru",
- "steancomminyty.com",
- "steancomminyty.ru.com",
- "steancommiuniliy.ru.com",
- "steancommiunity.com",
- "steancommmunity.com",
- "steancommnnity.com",
- "steancommnuitty.com",
- "steancommnuity.com",
- "steancommnulty.com",
- "steancommnunity.ru",
- "steancommnunitytradeoffer.xyz",
- "steancommnunlty.ru",
- "steancommounity.com",
- "steancommrnity.com",
- "steancommueniliy.ru.com",
- "steancommuhity.com",
- "steancommuhity.ru",
- "steancommuineliy.ru.com",
- "steancommuiniliy.ru.com",
- "steancommuinty.ru",
- "steancommuinuty.ru",
- "steancommuity.com",
- "steancommuity.ru",
- "steancommumity.com",
- "steancommumity.net",
- "steancommumlty.com",
- "steancommuncity.ru",
- "steancommunety.com",
- "steancommunety.ru",
- "steancommunify.com",
- "steancommuniiity.com",
- "steancommuniiliy.ru.com",
- "steancommuniit.ru.com",
- "steancommuniite-xuz.ru",
- "steancommuniite.xyz",
- "steancommuniitty.com",
- "steancommuniity.com",
- "steancommuniity.fun",
- "steancommuniity.ru",
- "steancommunilly.com",
- "steancommunilty.com",
- "steancommunilty.ru",
- "steancommunily.ru",
- "steancommunite.site",
- "steancommuniti.com.ru",
- "steancommuniti.site",
- "steancommunitiy.com.ru",
- "steancommunitiy.ru",
- "steancommunitry.ru",
- "steancommunitty.com",
- "steancommunitty.xyz",
- "steancommunitv.com",
- "steancommunity.cc",
- "steancommunity.click",
- "steancommunity.host",
- "steancommunity.link",
- "steancommunity.net.ru",
- "steancommunity.pw",
- "steancommunity.ru.com",
- "steancommunity.ru",
- "steancommunitytradeaffer.xyz",
- "steancommunlity.ru.com",
- "steancommunllty.com",
- "steancommunlty.business",
- "steancommunlty.com",
- "steancommunlty.ru.com",
- "steancommunlty.ru",
- "steancommunmilty.com",
- "steancommunniitly.ru",
- "steancommunniity.ru",
- "steancommunnilty.ru",
- "steancommunnily.ru",
- "steancommunnitl.ru",
- "steancommunnitlly.ru",
- "steancommunnity.co",
- "steancommunnity.site",
- "steancommunnliity.ru",
- "steancommunnlity.ru",
- "steancommunnlty.com",
- "steancommunnlty.ru",
- "steancommunnty.com",
- "steancommunnuly.me",
- "steancommuntiy.ru.com",
- "steancommuntly.com",
- "steancommunuity.ru",
- "steancommunuty.com",
- "steancommunyti.com",
- "steancommunyti.ru.com",
- "steancommurily.xyz",
- "steancommutiny.ru",
- "steancommuuity.com",
- "steancommuuniliiy.ru.com",
- "steancommuuniliy.ru.com",
- "steancommuunity.com",
- "steancommuvity.com",
- "steancommynitu.com",
- "steancommynity.org.ru",
- "steancommynity.ru.com",
- "steancommynuti.ru",
- "steancommynyty.ru.com",
- "steancomnmunity.ru",
- "steancomnnunity.com",
- "steancomnnunnity.ru",
- "steancomnuilty.ru.com",
- "steancomnuity.com",
- "steancomnumity.com",
- "steancomnumlty.com",
- "steancomnumlty.ru",
- "steancomnuniiity.ru",
- "steancomnuniity.com",
- "steancomnunilty.ru",
- "steancomnunity.com",
- "steancomnunity.ru",
- "steancomnunitys.ru",
- "steancomnunlty.ru",
- "steancomnunnity.xyz",
- "steancomnunyti.ru.com",
- "steancomnunytu.ru.com",
- "steancomnunytu.ru",
- "steancomnurity.one",
- "steancomnurity.xyz",
- "steancomnuuniliy.ru.com",
- "steancomrnunitiy.com",
- "steancomrnunity.com",
- "steancomrnunity.ru",
- "steancomrnunuty.ru",
- "steancomuniiity.com",
- "steancomuniite-xuz.ru",
- "steancomuniity.com",
- "steancomunite-xuz.ru",
- "steancomunitiy.ru.com",
- "steancomunitly.ru",
- "steancomunity.ru.com",
- "steancomunitytradeffer.xyz",
- "steancomunnity.ru",
- "steancomunnity.tk",
- "steancomunnlty.me",
- "steancomunnlty.ru.com",
- "steancomunyiti.ru",
- "steancomunyti.ru.com",
- "steancomuunity.com",
- "steanconmnuity.com",
- "steanconmumity.com",
- "steanconmumlty.com",
- "steanconmunitiy.co",
- "steanconmunitly.ru",
- "steanconmunity.ru",
- "steanconmunlly.ru",
- "steanconmunlty.com",
- "steanconmunlty.ru",
- "steanconmunuty.ru",
- "steanconmunuty.xyz",
- "steanconmunyti.ru.com",
- "steanconmunyti.ru",
- "steanconmynmuti.com",
- "steanconnunitly.xyz",
- "steanconnunity.com",
- "steanconnunlty.com",
- "steancoommuniity.xyz",
- "steancoommunity.com",
- "steancoommunity.xyz",
- "steancoommunitytradeofferr.com",
- "steancoommunnity.com",
- "steancoomnuity.com",
- "steancoomnunity.com",
- "steancoomunnity.com",
- "steancornminuty.com",
- "steancornmunuty.ru",
- "steancouminnuty.org",
- "steanecommunlty.site",
- "steanfocuak.ru",
- "steanfocusd.xyz",
- "steanfocusi.ru",
- "steanfocusk.ru",
- "steanfocusse.ru",
- "steanfocussi.ru",
- "steanmcommuniitiy.ru",
- "steanmcommunily.ru",
- "steanmcommunity.com",
- "steanmcommunity.ru.com",
- "steanmcommunity.ru",
- "steanmcommuniuty.ru.com",
- "steanmcommunlty.ru",
- "steanmcommunlty.xyz",
- "steanmcommzunity.ru",
- "steanmcomnuinmty.com",
- "steanmcomnuity.com",
- "steanmcomnumntiy.com",
- "steanmcomnumty.com",
- "steanmcomnunitiy.com",
- "steanmcomnunity.com",
- "steanmcomnynuytiy.org.ru",
- "steanmcomrninuty.xyz",
- "steanmcomumnity.xyz",
- "steanmcomunitly.ru",
- "steanmconmunity.com",
- "steanmconmunnity.ru",
- "steanmconnynuytiy.net.ru",
- "steanmconynnuytiy.net.ru",
- "steanmconynnuytiy.org.ru",
- "steanmecommunity.com",
- "steanmncommunity.com",
- "steanmncomnunity.com",
- "steanncammunlte.com",
- "steanncammunlte.ru",
- "steanncmmunytiy.ru",
- "steanncomminity.ru.com",
- "steanncommity.co",
- "steanncommiuty.com",
- "steanncommnunyti.com",
- "steanncommuiniuty.com",
- "steanncommunily.com",
- "steanncommunitv.com",
- "steanncommunity.com",
- "steanncommuniuity.com",
- "steanncommunlty.com",
- "steanncomnmunity.com",
- "steanncomnuniity.com",
- "steanncomnuniity.online",
- "steanncomnuniity.ru",
- "steanncomnuniity.xyz",
- "steanncomnunity.xyz",
- "steanncomunitiy.ru.com",
- "steanncomunitli.ru.com",
- "steanncomunitly.co",
- "steanncomunitly.ru.com",
- "steanncomunitly.ru",
- "steanncomunitty.site",
- "steanncomunity.com",
- "steanncomunnity.ru",
- "steannconmunity.com",
- "steannconnmunity.com",
- "steannconnnnunity.net.ru",
- "steannconnnunity.com",
- "steannconnunynity.ru",
- "steannecomunlty.com",
- "steanpowered.net.ru",
- "steanpowered.xyz",
- "steanrcommunitiy.com",
- "steapowered.com",
- "steappowered.com",
- "stearamcomminnity.net",
- "stearamcomnunitu.xyz",
- "stearcommity.com",
- "stearcommuity.com",
- "stearcommunitly.com",
- "stearmcammunity.com",
- "stearmcommnity.com",
- "stearmcommnumity.com",
- "stearmcommnunity.com",
- "stearmcommnunnity.org",
- "stearmcommrunity.com",
- "stearmcommuniity.com",
- "stearmcommuniity.ru.com",
- "stearmcommuninty.com",
- "stearmcommunitly.ru",
- "stearmcommunitry.cf",
- "stearmcommunitty.ru.com",
- "stearmcommunity.com",
- "stearmcommunity.one",
- "stearmcommunity.ru.com",
- "stearmcommunltly.com",
- "stearmcommunnitty.online",
- "stearmcommunnity.ru.com",
- "stearmcommuunity.ru.com",
- "stearmcommuunity.ru",
- "stearmcommuunnity.ru",
- "stearmcommynity.fun",
- "stearmcomrmunity.co",
- "stearmcomrmunity.com",
- "stearmcomrnunitiy.com",
- "stearmcomrnunity.com",
- "stearmconmmunity.com",
- "stearmconmunity.ru",
- "stearmconmunnity.com",
- "stearmconnrnunity.com",
- "stearmcormmunity.com",
- "stearmcornmunitiy.com",
- "stearmcornmunity.ru",
- "stearmcornmunlty.com",
- "stearmcornnnunity.com",
- "stearmmcommuniity.ru",
- "stearmmcomunitty.ru",
- "stearmmcomunity.ru",
- "stearmmcomuunity.ru",
- "stearncomiunity.ru",
- "stearncomminhty.com",
- "stearncomminutiu.ru",
- "stearncomminuty.click",
- "stearncomminuty.com",
- "stearncomminuty.link",
- "stearncomminuty.ru.com",
- "stearncomminuty.ru",
- "stearncomminytu.com",
- "stearncommiunity.com",
- "stearncommiuty.co",
- "stearncommmnuity.xyz",
- "stearncommmunity.online",
- "stearncommmunity.ru",
- "stearncommninuty.com",
- "stearncommnniity.com",
- "stearncommnniity.ru",
- "stearncommnnity.co.uk",
- "stearncommnnity.com",
- "stearncommnuinty.com",
- "stearncommnuity.ru.com",
- "stearncommnunity.ru.com",
- "stearncommonity.ru",
- "stearncommrunity.com",
- "stearncommubity.com",
- "stearncommuinuty.co",
- "stearncommumitly.com",
- "stearncommumity.com",
- "stearncommumlty.com",
- "stearncommunety.com",
- "stearncommunety.ru",
- "stearncommungty.com",
- "stearncommunhty.com",
- "stearncommunigy.com",
- "stearncommuniitty.xyz",
- "stearncommuniity.click",
- "stearncommuniity.ru",
- "stearncommuniity.site",
- "stearncommuniityt.click",
- "stearncommunilly.site",
- "stearncommunilty.ru",
- "stearncommunilty.site",
- "stearncommunily.ru",
- "stearncommunily.website",
- "stearncommuninity.com",
- "stearncommuniry.com",
- "stearncommunite.com",
- "stearncommunitey.com",
- "stearncommunitey.ru",
- "stearncommunitly.ru",
- "stearncommunitly.website",
- "stearncommunitly.xyz",
- "stearncommunity.click",
- "stearncommunity.link",
- "stearncommunity.net.ru",
- "stearncommunity.ru",
- "stearncommunivy.com",
- "stearncommunjty.com",
- "stearncommunlity.com",
- "stearncommunlty.ru",
- "stearncommunlty.site",
- "stearncommunlty.store",
- "stearncommunnitty.xyz",
- "stearncommunnity.ru",
- "stearncommunnity.xyz",
- "stearncommunrty.com",
- "stearncommuntity.com",
- "stearncommuntiy.com",
- "stearncommuntty.com",
- "stearncommunuitiy.com",
- "stearncommunuity.net.ru",
- "stearncommunutiy.com",
- "stearncommunyti.ru",
- "stearncommunytiy.ru",
- "stearncommunytiyu.ru",
- "stearncommurity.ru",
- "stearncommutiny.online",
- "stearncommutiny.ru",
- "stearncommuty.com",
- "stearncommynitu.ru.com",
- "stearncommynity.fun",
- "stearncommynity.ru.com",
- "stearncomnmunity.com",
- "stearncomnnunity.fun",
- "stearncomnnunity.site",
- "stearncomnnunity.website",
- "stearncomnnunty.com.ru",
- "stearncomnumity.com",
- "stearncomnunily.com",
- "stearncomnunitu.ru",
- "stearncomnunitv.ru.com",
- "stearncomnunity.com",
- "stearncomnunity.org",
- "stearncomnunity.ru.com",
- "stearncomnunnity.ru",
- "stearncomrmunity.co",
- "stearncomrmunity.com",
- "stearncomrmynity.fun",
- "stearncomrninuty.ru",
- "stearncomrninuty.xyz",
- "stearncomrnrunity.ru.com",
- "stearncomrnrunity.ru",
- "stearncomrnunety.com",
- "stearncomrnunitly.site",
- "stearncomrnunitly.xyz",
- "stearncomrnunity.com",
- "stearncomrnunity.ru",
- "stearncomrnunity.store",
- "stearncomrnunlity.ru",
- "stearncomrnunlty.site",
- "stearncomrnunyti.ru",
- "stearncomrrnunity.com",
- "stearncomrrunity.com",
- "stearncomrunity.ru.com",
- "stearncomrunity.ru",
- "stearncomunitu.ru",
- "stearncomunlty.ru.com",
- "stearncomynity.ru",
- "stearnconmumity.com",
- "stearnconmunity.com",
- "stearnconmunity.me",
- "stearnconmunity.net",
- "stearnconmuntiy.ru",
- "stearnconmuuity.com",
- "stearnconmuulty.ru",
- "stearnconnrnunity.xyz",
- "stearnconrmunity.com",
- "stearncormmunity.com",
- "stearncormmunity.ru",
- "stearncormunity.ru",
- "stearncormunniti.org",
- "stearncornminuty.com",
- "stearncornminuty.ru",
- "stearncornmnuity.ru",
- "stearncornmrunity.ru.com",
- "stearncornmunitiy.com",
- "stearncornmunitly.com",
- "stearncornmunity.com",
- "stearncornmunity.net",
- "stearncornmunity.ru.com",
- "stearncornmunity.ru",
- "stearncornmunlty.ru",
- "stearncornmunuty.ru",
- "stearncornmurnity.ru.com",
- "stearncornnumyty.com",
- "stearncornnunity.ru",
- "stearncornrnnity.ru.com",
- "stearncornrnuity.com",
- "stearncornrnunity.com",
- "stearncornrnunity.ru.com",
- "stearncornunity.ru",
- "stearncornunity.xyz",
- "stearncornurniity.xyz",
- "stearncorrmunity.com",
- "stearncurnmunity.com",
- "stearnmcommunnity.com",
- "stearnmcomunity.com",
- "stearnncomrnunitiy.com",
- "stearnncomrnunity.com",
- "stearnporewed.ru.com",
- "stearnpovvered.com",
- "stearnpowered.online",
- "stearnpowered.xyz",
- "steasmpowered.com",
- "steawcammunity.xyz",
- "steawcommunity.com",
- "steawcommunity.net",
- "steawcomunity.net",
- "steawconnunity.xyz",
- "steawmcommunity.net",
- "steawmcomnunnity.ru",
- "steawmcomuunity.ru",
- "steawmcowmunnity.ru",
- "steawmpowered.com",
- "steawncomnunity.ru",
- "steawpowered.com",
- "steawscommunity.net",
- "steaxmcommity.com",
- "steeaamcomunity.xyz",
- "steeacmcommumitiy.com",
- "steeamcommmunety.com",
- "steeamcommmunitty.site",
- "steeamcommmunity.com",
- "steeamcommuinitty.com",
- "steeamcommunity.me",
- "steeamcommunity.ml",
- "steeamcommunity.ru.com",
- "steeamcommunlity.com",
- "steeamcommunlity.ru",
- "steeamcommunllty.xyz",
- "steeamcommunlty.com",
- "steeamcommunnity.ru.com",
- "steeamcommunnity.ru",
- "steeamcommunnlty.ru",
- "steeamcommunnuity.ru.com",
- "steeamcommunyti.com",
- "steeamcomnnunity.com",
- "steeamcomuneety.com",
- "steeamcomunitty.com",
- "steeamcomunity.net",
- "steeamcomunlty.ru.com",
- "steeamcomunlty.ru",
- "steeamcomunnlty.com",
- "steeamcoommunity.ru",
- "steeammcomunity.com",
- "steeammcomunlty.com",
- "steeampowered.tk",
- "steeamwins.xyz",
- "steemacommunity.com",
- "steemcammunllty.com",
- "steemcammunlly.com",
- "steemcammunlty.com",
- "steemcommmunety.com",
- "steemcommmunity.com",
- "steemcommnnity.com",
- "steemcommnunity.ru",
- "steemcommnunnity.ru.com",
- "steemcommuinty.com",
- "steemcommuniity.com",
- "steemcommunily.ru.com",
- "steemcommuninity.org.ru",
- "steemcommuniry.com",
- "steemcommunitey.com",
- "steemcommuniti.com",
- "steemcommunitry.com",
- "steemcommunity.co",
- "steemcommunity.com",
- "steemcommunity.ru.com",
- "steemcommunityy.com",
- "steemcommuniy.com",
- "steemcommunllty.com",
- "steemcommunlty.com",
- "steemcommunly.com",
- "steemcommunnity.co",
- "steemcommunnity.net",
- "steemcommuntiy.ru.com",
- "steemcommuntiy.ru",
- "steemcommunty.net.ru",
- "steemcommunty.org.ru",
- "steemcommunty.pp.ru",
- "steemcommunty.ru",
- "steemcommuunity.com",
- "steemcommynity.ru",
- "steemcomnmunity.com",
- "steemcomnrunity.com",
- "steemcomrnunity.co",
- "steemcomrnunity.com",
- "steemcomrunity.ru",
- "steemcomunatlytradeoffer40034231.ru",
- "steemcomuniti.com",
- "steemcomuniti.ru",
- "steemcomunity.me",
- "steemcomunity.net.ru",
- "steemcomunity.org.ru",
- "steemcomunity.pp.ru",
- "steemcomunnity.com",
- "steemconnunity.com",
- "steemcoommunity.com",
- "steemcoommunity.ru",
- "steemcoommunlty.ru",
- "steemcoommuntiy.ru",
- "steemcoommunty.ru",
- "steemcoomnunty.ru",
- "steemcoomunity.xyz",
- "steemcoomuntiy.ru",
- "steemcoomuunity.ru",
- "steemcoonmuntiy.ru",
- "steemcowwunity.xyz",
- "steempowerd.ru",
- "steempowered.com",
- "steemurl.com",
- "steencommunilty.com",
- "steencommunityy.xyz",
- "steiamcommuinity.com",
- "steiamcommunityi.com",
- "steimcomnunnity.ru.com",
- "stemacommunity.net",
- "stemacommunlty.com",
- "stemacomunity.com",
- "stemapowered.com",
- "stemcammuniety.ru",
- "stemcammuniity.com",
- "stemcammuniity.ru",
- "stemcamnunity.com",
- "stemcamnunity.ru",
- "stemccomnmunity.com",
- "stemcomiunity.ru",
- "stemcomminity.com",
- "stemcomminuty.ru",
- "stemcommlunity.com",
- "stemcommnuity.ru.com",
- "stemcommnunity.com",
- "stemcommnunity.ru.com",
- "stemcommnunlty.ru",
- "stemcommnunnity.com",
- "stemcommnunulty.com",
- "stemcommnuunity.com",
- "stemcommouniity.com",
- "stemcommounilty.com",
- "stemcommounity.ru.com",
- "stemcommuinty.ru",
- "stemcommuniby.com",
- "stemcommuniety.com",
- "stemcommuniity.com",
- "stemcommuniity.ru",
- "stemcommunilty.com",
- "stemcommunilty.ru",
- "stemcommunite.pp.ru",
- "stemcommuniti.ru",
- "stemcommunitiy.com",
- "stemcommunitly.com",
- "stemcommunitty.com",
- "stemcommunitty.ru.com",
- "stemcommunity.com.ru",
- "stemcommunity.ru.com",
- "stemcommunity.ru",
- "stemcommunitytraade.xyz",
- "stemcommunitytrade.com",
- "stemcommunitytrade.fun",
- "stemcommunjty.com",
- "stemcommunlitly.com",
- "stemcommunlity.ru",
- "stemcommunlty.com",
- "stemcommunlty.ru.com",
- "stemcommunlty.space",
- "stemcommunniity.com",
- "stemcommunnilty.com",
- "stemcommunnitiy.net.ru",
- "stemcommunnity.com.ru",
- "stemcommunnity.com",
- "stemcommunuity.com",
- "stemcommununity.com",
- "stemcommuty.ru",
- "stemcommuunity.com.ru",
- "stemcommynity.ru.com",
- "stemcommyunity.ru",
- "stemcomnmnnunity.com",
- "stemcomnmnunity.com",
- "stemcomnmounity.com",
- "stemcomnmuity.com",
- "stemcomnmuniity.com",
- "stemcomnmuniity.ru.com",
- "stemcomnmunity.com.ru",
- "stemcomnmunity.ru.com",
- "stemcomnmunity.ru",
- "stemcomnmunniity.com",
- "stemcomnmunnity.com",
- "stemcomnmunuity.com",
- "stemcomnmununity.com",
- "stemcomnmuunity.com",
- "stemcomnmuunity.ru.com",
- "stemcomnnmunity.com",
- "stemcomnnmunnity.com",
- "stemcomnnmuunity.ru",
- "stemcomnuniti.ru",
- "stemcomnunity.com",
- "stemcomnunity.ru.com",
- "stemcomnunity.ru",
- "stemcomnunyti.ru.com",
- "stemcomrnmunity.com",
- "stemcomrnuniity.ru",
- "stemcomuniti.ru",
- "stemcomunitiy.com",
- "stemcomunity.com",
- "stemcomunity.net",
- "stemcomunity.ru.com",
- "stemcomunnity.com.ru",
- "stemcomunnity.com",
- "stemcomunnity.ru.com",
- "stemconmmnunity.com",
- "stemconmmunity.com",
- "stemconmmunnity.com",
- "stemconmmuunnity.com",
- "stemconmnmuunity.com",
- "stemconmuite.xyz",
- "stemconmumity.ru",
- "stemcoominuty-alirdrop.xyz",
- "stemcoommounity.com",
- "stemcoommuniity.com",
- "stemcoommunity.com",
- "stemcoommuunnity.com",
- "stemcoomnmnunity.com",
- "stemcoomnmounity.com",
- "stemcoomnmuniity.com",
- "stemcoomnmunity.com",
- "stemcoomnmunity.ru.com",
- "stemcoomnmunnity.com",
- "stemcoomnnunity.com",
- "stemcormmunity.com",
- "stemcormmunlty.ru.com",
- "stemcornmunitly.ru.com",
- "stemcornmunity.com",
- "stemcornmunity.ru.com",
- "stemcornmunity.ru",
- "stemcornmunlty.xyz",
- "stemcummnuity.ru.com",
- "stemcummnunity.ru.com",
- "stemcummunity.com.ru",
- "stemcummunity.ru.com",
- "stemcummunnity.com.ru",
- "stemcummunnity.ru.com",
- "stemcumnmunity.com.ru",
- "stemcumnmunity.com",
- "stemcumnmunity.ru.com",
- "stemcumunnity.ru.com",
- "stemecommunlty.com",
- "stemmcomunity.xyz",
- "stemmcomunnityy.xyz",
- "stemncornmunity.com",
- "stemsell.ml",
- "stencommunity.com",
- "stenmcommunilty.ru.com",
- "stenmcommunitly.ru.com",
- "stenncornmuniy.com",
- "stennicommuitun.com",
- "steomcommunitey.com",
- "steomcommunito.con",
- "steomcommunity.com",
- "steomcommunity.ru",
- "steomcommunlty.ml",
- "steomcomnunity.ru.com",
- "steomconmunity.com",
- "steomcoommynity.ru.com",
- "stepmscononnity.com",
- "steqmcommunity.com",
- "steqmpowered.com",
- "steramconmunity.com",
- "sterampowered.com",
- "stermccommunitty.ru",
- "stermcommuniity.com",
- "stermcommunilty.ru.com",
- "stermcommunity.com",
- "stermcommunity.ru.com",
- "stermcommunityy.ru",
- "stermcommunlity.ru.com",
- "stermcommunnitty.ru",
- "stermcomunitte.xyz",
- "stermcomunniity.ru",
- "stermconmmunity.com",
- "stermmcomuniity.ru",
- "stermncommunity.com",
- "sterncommunilty.ru.com",
- "sterncommunilty.site",
- "sterncommunnity.ru",
- "sterncommynuty.ru",
- "sterncomnurity.one",
- "sternconmunity.ru",
- "sterncornmunity.ru",
- "sternmcommunity.com",
- "sternmconmunity.com",
- "sternmcornmmunity.com",
- "sternmcornnunity.com",
- "sterumcommunity.com",
- "stetrncommity.com",
- "steumcommunity.com",
- "steumcommunity.ru",
- "steumcornmunity.com",
- "steurmcommunity.com",
- "steurmconmunity.com",
- "stewie2k-giveaway-150days.pro",
- "stewmpowered.com",
- "stfriendprofile.ru",
- "stg.steamcpowered.com",
- "stheamcommnitiy.ru",
- "stheamcommuniti.com",
- "stheamcommunity.ru",
- "stheamcommunutiy.ru",
- "stheamcommunutly.ru",
- "stheamcomunitly.ru",
- "stheamcomunutly.ru",
- "stheamconmuniity.com",
- "stheamconnmunutly.ru",
- "stheamcornmunitiy.ru",
- "stiamcammunieti.com",
- "stiamcommunitly.xyz",
- "stiamcommunity.com",
- "stiamcommyunlty.ru.com",
- "stiamcomunity.xyz",
- "stiamcomunlty.ru",
- "stiamcomynity.com",
- "stieamcommuinity.com",
- "stieamcommuniity.com",
- "stieamcommuniity.ru",
- "stieamcommunitey.ru",
- "stieamcommunitiy.com",
- "stieamcommunity.com",
- "stieamcommunity.org.ru",
- "stieamcommunity.pp.ru",
- "stieamcommuunitey.us",
- "stieamcommynituy.com",
- "stieamcomnnunity.com",
- "stieamcomuniiti.ru",
- "stieamcomunity.com",
- "stieamconmuniity.com",
- "stieamconnmunity.com",
- "stieamcormnynity.ru.com",
- "stiemcommunitty.ru",
- "stiemconnumity.xyz",
- "stimcommunity.ru",
- "stimcommunlty.ru",
- "stimiache.ru",
- "stjeamcoimmunity.com",
- "stjeamcommunity.ru",
- "stjeamcomnuminiti.ru",
- "stjeamcomnunitiy.ru",
- "stjeamcomnunity.ru",
- "stjeamcomuniity.ru",
- "stjeamconmunnitii.com",
- "stleaamcommunity.com",
- "stleam-communithy.com",
- "stleamcommiunity.ru.com",
- "stleamcommiynitu.ru",
- "stleamcommiynitu.xyz",
- "stleamcommiynity.xyz",
- "stleamcommnunity.ru",
- "stleamcommulnity.xyz",
- "stleamcommulnitycom.xyz",
- "stleamcommuneety.com",
- "stleamcommuniity.com",
- "stleamcommuniity.net",
- "stleamcommunilty.com",
- "stleamcommunithy.com",
- "stleamcommunitiy.com",
- "stleamcommunitly.com",
- "stleamcommunitty.com",
- "stleamcommunity.com",
- "stleamcommunity.net",
- "stleamcommunlty.com",
- "stleamcommunlty.xyz",
- "stleamcomnmunity.ru.com",
- "stleamcomnunity.ru.com",
- "stleamcomunity.com",
- "stleamconminity.online",
- "stleamconminity.ru",
- "stleamconmmunity.ru.com",
- "stleamconmmunlty.net.ru",
- "stleamconmunity.com",
- "stleamconnunlty-tyztradeoffernewpartnhr15902271.xyz",
- "stleamcormmunity.ru.com",
- "stleamcormmynity.ru.com",
- "stleamcormunity.ru.com",
- "stleamcornmmunity.ru.com",
- "stleammcomnnunitycom.buzz",
- "stleamncommunity.ru",
- "stleancommunity.ru",
- "stleanmcommunity.ru",
- "stleaomcoommynity.ru.com",
- "stlemamcornmunty.me",
- "stmawards.xyz",
- "stmcornnunnitty.xyz",
- "stmcornumnunitty.xyz",
- "stmeacomunnitty.ru",
- "stmemcomyunity.com",
- "stmencommunity.ru",
- "stmtrdoffer.xyz",
- "stoacommunity.codes",
- "stoemcommunity.com",
- "stopify.com",
- "store-communitiy.com",
- "store-discord.com",
- "store-steam-csgo.ru",
- "store-steamcomminuty.ru.com",
- "store-steamcommunity.xyz",
- "store-steamcomnunity",
- "store-steampoweered.ru",
- "store-steampowereb.com",
- "store-steampowered.ru",
- "store-stempowered.com",
- "store-streampowered.me",
- "store.stampowered.com",
- "store.stempowerd.com",
- "storeesteampowered.ru.com",
- "storeesteampowereed.ru.com",
- "stores-steampowered.com",
- "storesleampowecommunity.store",
- "storesteam-csgo.ru",
- "straemcommonlity.com",
- "straemcomunnitry.ru",
- "straemcummonilty.com",
- "straemcummonity.com",
- "stramconmunity.com",
- "strcomnunnitly.xyz",
- "streaalcommuunnitu.ru",
- "streaemcrommunlty.com.ru",
- "stream-conmunlty.ru",
- "streamc0mmunnlty.xyz",
- "streamcammunitly.com",
- "streamccomunilty.com",
- "streamcolmnty.xyz",
- "streamcomlutitly.me",
- "streamcomminuty.pw",
- "streamcomminuty.ru.com",
- "streamcommiumity.com",
- "streamcommiunity.com",
- "streamcommiunnity.com",
- "streamcommlunity.ru.com",
- "streamcommmumnity.ru.com",
- "streamcommmunify.ru.com",
- "streamcommmunitty.ru.com",
- "streamcommmunity.com",
- "streamcommmunjty.ru.com",
- "streamcommmunlty.ru.com",
- "streamcommmunnlty.ru.com",
- "streamcommnnity.com",
- "streamcommnnuity.com",
- "streamcommnnutiy.com",
- "streamcommnuity.com",
- "streamcommnuity.ru",
- "streamcommnunilty.com",
- "streamcommnunitly.com",
- "streamcommnunity.ru",
- "streamcommnunlity.ru",
- "streamcommnunnity.ml",
- "streamcommnunuty.ru.com",
- "streamcommnunuty.ru",
- "streamcommonlty.ru.com",
- "streamcommounity.com",
- "streamcommuinity.com",
- "streamcommuinty.com",
- "streamcommuiny.ru",
- "streamcommulinty.com",
- "streamcommulnty.com",
- "streamcommumity.ru.com",
- "streamcommumninty.com",
- "streamcommumnity.com",
- "streamcommumtiy.ru",
- "streamcommunaly.com",
- "streamcommunaty.com",
- "streamcommuneiley.net",
- "streamcommunetly.com",
- "streamcommunety.ru",
- "streamcommunicate.ru",
- "streamcommunication.com",
- "streamcommunify.com",
- "streamcommuniiley.net.ru",
- "streamcommuniiley.net",
- "streamcommuniily.com",
- "streamcommuniitty.com",
- "streamcommuniitu.com",
- "streamcommuniity.org",
- "streamcommuniity.ru.com",
- "streamcommuniity.ru",
- "streamcommuniityy.me",
- "streamcommuniley.net.ru",
- "streamcommuniley.net",
- "streamcommuniliey.net.ru",
- "streamcommuniliey.xyz",
- "streamcommuniliiey.net.ru",
- "streamcommuniliiey.org.ru",
- "streamcommuniliiey.pp.ru",
- "streamcommuniliiy.org.ru",
- "streamcommuniliiy.pp.ru",
- "streamcommunillty.com",
- "streamcommunilly.com",
- "streamcommunilty.com",
- "streamcommunilty.xyz",
- "streamcommunily.cc",
- "streamcommunily.co",
- "streamcommunily.com",
- "streamcommunily.icu",
- "streamcommunily.me",
- "streamcommunily.net",
- "streamcommunily.ru.com",
- "streamcommunimty.com",
- "streamcommuninllty.com",
- "streamcommuninnity.com",
- "streamcommuninnuity.com",
- "streamcommuninty.com",
- "streamcommuninty.me",
- "streamcommuninuty.store",
- "streamcommunit.com",
- "streamcommunit.ru.com",
- "streamcommunite.com",
- "streamcommunite.ru.com",
- "streamcommunitey.com",
- "streamcommuniti.ru",
- "streamcommuniti.xyz",
- "streamcommunitily.com",
- "streamcommunitiy.com",
- "streamcommunitiy.net",
- "streamcommunitiy.ru.com",
- "streamcommunitiy.ru",
- "streamcommunitly.net",
- "streamcommunitly.ru",
- "streamcommunitly.xyz",
- "streamcommunitry.ru",
- "streamcommunitty.ru.com",
- "streamcommunitu.com",
- "streamcommunitv.me",
- "streamcommunitv.net",
- "streamcommunity-user.me",
- "streamcommunity.com.ru",
- "streamcommunity.me",
- "streamcommunity.net.ru",
- "streamcommunity.one",
- "streamcommunity.org.ru",
- "streamcommunity.pl",
- "streamcommunity.ru.com",
- "streamcommunityi.ru",
- "streamcommunityy.me",
- "streamcommuniunity.com",
- "streamcommuniuty.ru.com",
- "streamcommuniuty.store",
- "streamcommuniy.ru",
- "streamcommunjty.com",
- "streamcommunjty.ru.com",
- "streamcommunlity.ru",
- "streamcommunliy.com",
- "streamcommunlte.ru",
- "streamcommunltiy.com",
- "streamcommunlty.net",
- "streamcommunly.com",
- "streamcommunly.me",
- "streamcommunly.net",
- "streamcommunly.ru",
- "streamcommunminty.com",
- "streamcommunmity.com",
- "streamcommunniity.com",
- "streamcommunnilty.com",
- "streamcommunnitty.com",
- "streamcommunnity.org",
- "streamcommunnty.com",
- "streamcommunnty.me",
- "streamcommunnuitty.com",
- "streamcommuntiiy.org",
- "streamcommuntiy.com",
- "streamcommuntly.com",
- "streamcommuntly.net.ru",
- "streamcommuntly.org.ru",
- "streamcommuntly.pp.ru",
- "streamcommunttly.com",
- "streamcommunty.co",
- "streamcommunty.me",
- "streamcommunty.ru",
- "streamcommunuitty.com",
- "streamcommunuity.net",
- "streamcommununty.com",
- "streamcommuny.ru",
- "streamcommunyty.com",
- "streamcommutiny.net",
- "streamcommuuniity.com",
- "streamcommuunilty.ru.com",
- "streamcommuunity.com",
- "streamcommuunniity.com",
- "streamcommuunnity.com",
- "streamcommuunnity.net",
- "streamcommuuty.ru",
- "streamcommynitu.com",
- "streamcommynuty.com",
- "streamcomninuty.xyz",
- "streamcomnmunity.ru.com",
- "streamcomnmunnity.ru.com",
- "streamcomnnunity.net",
- "streamcomnnunity.website",
- "streamcomnnunity.xyz",
- "streamcomnnunlty.com",
- "streamcomnnunuty.com",
- "streamcomnully.net.ru",
- "streamcomnully.org.ru",
- "streamcomnullyty.net.ru",
- "streamcomnullyty.org.ru",
- "streamcomnullyty.pp.ru",
- "streamcomnultyy.net.ru",
- "streamcomnultyy.org.ru",
- "streamcomnumity.ru",
- "streamcomnumnity.ru.com",
- "streamcomnunely.com",
- "streamcomnunetiy.com",
- "streamcomnuniity.com",
- "streamcomnuniity.net",
- "streamcomnunitiy.ru",
- "streamcomnunitly.ru",
- "streamcomnunitry.ru",
- "streamcomnunitty.com",
- "streamcomnunity.ru",
- "streamcomnunity.site",
- "streamcomnuniuty.com",
- "streamcomnunlity.com",
- "streamcomnunlty.ru",
- "streamcomnunnity.ru",
- "streamcomnunuty.com",
- "streamcomnunuty.ru",
- "streamcomnunyti.xyz",
- "streamcomrnunitiy.ru",
- "streamcomrnunity.com",
- "streamcomrnunity.online",
- "streamcomrnunity.ru",
- "streamcomulty.net.ru",
- "streamcomulty.org.ru",
- "streamcomuniitty.ru.com",
- "streamcomuniity.cf",
- "streamcomuniity.com",
- "streamcomuniity.net",
- "streamcomuniity.pp.ua",
- "streamcomunilty.net.ru",
- "streamcomunilty.org.ru",
- "streamcomunily.net.ru",
- "streamcomunily.org.ru",
- "streamcomunily.pp.ru",
- "streamcomunitly.com",
- "streamcomunitly.net.ru",
- "streamcomunitly.net",
- "streamcomunitly.ru",
- "streamcomunitry.com",
- "streamcomunitty.net",
- "streamcomunitu.ru",
- "streamcomunity.com",
- "streamcomunity.fun",
- "streamcomunity.net",
- "streamcomunity.org",
- "streamcomunity.ru.com",
- "streamcomunlty.net.ru",
- "streamcomunlty.org.ru",
- "streamcomunlty.pp.ru",
- "streamcomunltyy.org.ru",
- "streamcomunltyy.pp.ru",
- "streamcomunniity.net.ru",
- "streamcomunnity.pp.ua",
- "streamcomunnity.ru.com",
- "streamcomunnity.xyz",
- "streamcomuuniltyy.org.ru",
- "streamcomuuniltyy.pp.ru",
- "streamcomuunltyy.net.ru",
- "streamcomuunltyy.org.ru",
- "streamcomuunltyy.pp.ru",
- "streamcomynity.com",
- "streamcomynity.ru.com",
- "streamconmmunity.com",
- "streamconmmunity.ru.com",
- "streamconmumuty.xyz",
- "streamconmunilty.com",
- "streamconmunitly.com",
- "streamconmunitly.ru",
- "streamconmunity.com",
- "streamconmunlity.com",
- "streamconmunlty.ru",
- "streamconmunyti.com",
- "streamconnmunity.com",
- "streamconnuity.com",
- "streamconnumity.com",
- "streamconnunitly.com",
- "streamconnunity.net.ru",
- "streamconnunity.ru",
- "streamconnunity.site",
- "streamconnunity.us",
- "streamconunity.net.ru",
- "streamcoommounity.com",
- "streamcoommuniity.xyz",
- "streamcoommunity.com",
- "streamcoommunity.net",
- "streamcoommunity.xyz",
- "streamcormmunity.com",
- "streamcormmunity.ru.com",
- "streamcormmunlty.ru.com",
- "streamcormmunnity.ru.com",
- "streamcormmyniity.ru.com",
- "streamcormnmunity.ru.com",
- "streamcormunnity.ru.com",
- "streamcornnunitly.co",
- "streamcornnunitly.com",
- "streamcoumunniity.org",
- "streamcoumunnity.org",
- "streamcrommunify.me",
- "streamcummonity.ru.com",
- "streamcummunity.ru.com",
- "streamcummunlty.com",
- "streamcummunlty.xyz",
- "streamecommuniity.com",
- "streamecommunity.com",
- "streammcommunity.ru",
- "streammcomunittty.ru",
- "streammcomunity.com",
- "streammcomunnity.ru",
- "streammcomuunity.ru",
- "streammcornmunnity.com",
- "streamncommnunity.com",
- "streamnconmumity.com",
- "streamnconmunity.com",
- "streamnconmunity.ru",
- "streampoered.com",
- "streampowered.store",
- "streampowereed.com",
- "streancommumity.ru.com",
- "streancommuniity.ru.com",
- "streancommuniliy.ru.com",
- "streancommuniliy.ru",
- "streancommunitiy.co",
- "streancommunitiy.net.ru",
- "streancommunitiy.ru",
- "streancommunity.ru.com",
- "streancommunuty.ru",
- "streancomunnitiy.com",
- "streancomunnuty.com",
- "streancoommunity.com",
- "streancoommunity.xyz",
- "streanncomminity.ru",
- "streanncommunity.space",
- "streanncomnnunuty.com",
- "streanncomunity.ru",
- "strearmcommunity.ru",
- "strearmcomunity.ru",
- "strearncomuniity.ru.com",
- "streawcommunity.xyz",
- "streeamcommunuti.ru",
- "streemcommunhity.org.ru",
- "streemcommunitiy.ru.com",
- "strempowered.com",
- "streomcommunuty.com",
- "strieamcommunniity.com",
- "striieamcomnmunniitty.ru",
- "stteamcommiunity.com",
- "stteamcommunitty.com",
- "stteamcommunity.net",
- "sttemcomnmuty.ru.com",
- "stuamcommnuity.com",
- "stuamcommunity.com",
- "stuemconmunity.com",
- "sturemconmunity.com",
- "stwsmarket.ru",
- "styamcommunity.com",
- "styeampowerd.com",
- "styeampowered.com",
- "stzeamcomnumiti.ru",
- "sueamcommunity.com",
- "sueamconmunity.com",
- "sufficienttime.rocks",
- "summer-rust.xyz",
- "sunnygamble.com",
- "superbalancednow.com",
- "superdealgadgets.com",
- "support.verifiedbadgehelp-form.ml",
- "supremeskins.cf",
- "surveysandpromoonline.com",
- "swapskins.ga",
- "swapskins.live",
- "swapslot.tk",
- "sweet-fortune.ru",
- "ta-sty.info",
- "taceitt.com",
- "tacelt.com",
- "tacticalusa.com",
- "takeit100.xyz",
- "takeit101.xyz",
- "takeit102.xyz",
- "takeit103.xyz",
- "takeit104.xyz",
- "takeit105.xyz",
- "takeit106.xyz",
- "takeit107.xyz",
- "takeit108.xyz",
- "takeit109.xyz",
- "takeit110.xyz",
- "takeit111.xyz",
- "takeit112.xyz",
- "takeit113.xyz",
- "takeit114.xyz",
- "takeit115.xyz",
- "takeit116.xyz",
- "takeit117.xyz",
- "takeit118.xyz",
- "takeit119.xyz",
- "takeit120.xyz",
- "takeit121.xyz",
- "takeit122.xyz",
- "takeit123.xyz",
- "takeit124.xyz",
- "takeit125.xyz",
- "takeit126.xyz",
- "takeit127.xyz",
- "takeit128.xyz",
- "takeit129.xyz",
- "takeit130.xyz",
- "takeit131.xyz",
- "takeit132.xyz",
- "takeit133.xyz",
- "takeit134.xyz",
- "takeit135.xyz",
- "takeit136.xyz",
- "takeit137.xyz",
- "takeit138.xyz",
- "takeit139.xyz",
- "takeit140.xyz",
- "takeit141.xyz",
- "takeit142.xyz",
- "takeit143.xyz",
- "takeit144.xyz",
- "takeit145.xyz",
- "takeit146.xyz",
- "takeit147.xyz",
- "takeit148.xyz",
- "takeit149.xyz",
- "takeit150.xyz",
- "takeit151.xyz",
- "takeit152.xyz",
- "takeit153.xyz",
- "takeit154.xyz",
- "takeit155.xyz",
- "takeit156.xyz",
- "takeit157.xyz",
- "takeit158.xyz",
- "takeit159.xyz",
- "takeit160.xyz",
- "takeit161.xyz",
- "takeit162.xyz",
- "takeit163.xyz",
- "takeit164.xyz",
- "takeit165.xyz",
- "takeit166.xyz",
- "takeit167.xyz",
- "takeit168.xyz",
- "takeit169.xyz",
- "takeit170.xyz",
- "takeit171.xyz",
- "takeit172.xyz",
- "takeit173.xyz",
- "takeit174.xyz",
- "takeit175.xyz",
- "takeit176.xyz",
- "takeit177.xyz",
- "takeit178.xyz",
- "takeit179.xyz",
- "takeit20.xyz",
- "takeit21.xyz",
- "takeit22.xyz",
- "takeit23.xyz",
- "takeit24.xyz",
- "takeit25.xyz",
- "takeit26.xyz",
- "takeit260.xyz",
- "takeit261.xyz",
- "takeit262.xyz",
- "takeit263.xyz",
- "takeit264.xyz",
- "takeit265.xyz",
- "takeit266.xyz",
- "takeit267.xyz",
- "takeit268.xyz",
- "takeit269.xyz",
- "takeit27.xyz",
- "takeit270.xyz",
- "takeit271.xyz",
- "takeit272.xyz",
- "takeit273.xyz",
- "takeit274.xyz",
- "takeit275.xyz",
- "takeit276.xyz",
- "takeit277.xyz",
- "takeit278.xyz",
- "takeit279.xyz",
- "takeit28.xyz",
- "takeit280.xyz",
- "takeit281.xyz",
- "takeit282.xyz",
- "takeit283.xyz",
- "takeit284.xyz",
- "takeit285.xyz",
- "takeit286.xyz",
- "takeit287.xyz",
- "takeit288.xyz",
- "takeit289.xyz",
- "takeit29.xyz",
- "takeit290.xyz",
- "takeit291.xyz",
- "takeit292.xyz",
- "takeit293.xyz",
- "takeit294.xyz",
- "takeit295.xyz",
- "takeit296.xyz",
- "takeit297.xyz",
- "takeit298.xyz",
- "takeit299.xyz",
- "takeit30.xyz",
- "takeit300.xyz",
- "takeit301.xyz",
- "takeit302.xyz",
- "takeit303.xyz",
- "takeit304.xyz",
- "takeit305.xyz",
- "takeit306.xyz",
- "takeit307.xyz",
- "takeit308.xyz",
- "takeit309.xyz",
- "takeit31.xyz",
- "takeit310.xyz",
- "takeit311.xyz",
- "takeit312.xyz",
- "takeit313.xyz",
- "takeit314.xyz",
- "takeit315.xyz",
- "takeit316.xyz",
- "takeit317.xyz",
- "takeit318.xyz",
- "takeit319.xyz",
- "takeit32.xyz",
- "takeit321.xyz",
- "takeit322.xyz",
- "takeit323.xyz",
- "takeit324.xyz",
- "takeit325.xyz",
- "takeit326.xyz",
- "takeit327.xyz",
- "takeit328.xyz",
- "takeit329.xyz",
- "takeit33.xyz",
- "takeit330.xyz",
- "takeit331.xyz",
- "takeit332.xyz",
- "takeit333.xyz",
- "takeit334.xyz",
- "takeit335.xyz",
- "takeit336.xyz",
- "takeit337.xyz",
- "takeit338.xyz",
- "takeit339.xyz",
- "takeit34.xyz",
- "takeit340.xyz",
- "takeit341.xyz",
- "takeit342.xyz",
- "takeit343.xyz",
- "takeit344.xyz",
- "takeit345.xyz",
- "takeit346.xyz",
- "takeit347.xyz",
- "takeit348.xyz",
- "takeit349.xyz",
- "takeit35.xyz",
- "takeit350.xyz",
- "takeit351.xyz",
- "takeit352.xyz",
- "takeit353.xyz",
- "takeit354.xyz",
- "takeit355.xyz",
- "takeit356.xyz",
- "takeit357.xyz",
- "takeit358.xyz",
- "takeit359.xyz",
- "takeit36.xyz",
- "takeit360.xyz",
- "takeit361.xyz",
- "takeit362.xyz",
- "takeit363.xyz",
- "takeit364.xyz",
- "takeit365.xyz",
- "takeit366.xyz",
- "takeit367.xyz",
- "takeit368.xyz",
- "takeit369.xyz",
- "takeit37.xyz",
- "takeit370.xyz",
- "takeit371.xyz",
- "takeit372.xyz",
- "takeit373.xyz",
- "takeit374.xyz",
- "takeit375.xyz",
- "takeit376.xyz",
- "takeit377.xyz",
- "takeit378.xyz",
- "takeit379.xyz",
- "takeit38.xyz",
- "takeit380.xyz",
- "takeit381.xyz",
- "takeit382.xyz",
- "takeit383.xyz",
- "takeit384.xyz",
- "takeit385.xyz",
- "takeit386.xyz",
- "takeit388.xyz",
- "takeit389.xyz",
- "takeit39.xyz",
- "takeit390.xyz",
- "takeit391.xyz",
- "takeit392.xyz",
- "takeit393.xyz",
- "takeit394.xyz",
- "takeit395.xyz",
- "takeit396.xyz",
- "takeit397.xyz",
- "takeit398.xyz",
- "takeit399.xyz",
- "takeit40.xyz",
- "takeit400.xyz",
- "takeit401.xyz",
- "takeit402.xyz",
- "takeit403.xyz",
- "takeit404.xyz",
- "takeit405.xyz",
- "takeit406.xyz",
- "takeit407.xyz",
- "takeit408.xyz",
- "takeit409.xyz",
- "takeit41.xyz",
- "takeit410.xyz",
- "takeit411.xyz",
- "takeit412.xyz",
- "takeit413.xyz",
- "takeit414.xyz",
- "takeit415.xyz",
- "takeit416.xyz",
- "takeit417.xyz",
- "takeit418.xyz",
- "takeit419.xyz",
- "takeit42.xyz",
- "takeit420.xyz",
- "takeit422.xyz",
- "takeit423.xyz",
- "takeit424.xyz",
- "takeit425.xyz",
- "takeit426.xyz",
- "takeit427.xyz",
- "takeit428.xyz",
- "takeit429.xyz",
- "takeit43.xyz",
- "takeit430.xyz",
- "takeit431.xyz",
- "takeit432.xyz",
- "takeit433.xyz",
- "takeit434.xyz",
- "takeit435.xyz",
- "takeit436.xyz",
- "takeit437.xyz",
- "takeit438.xyz",
- "takeit439.xyz",
- "takeit44.xyz",
- "takeit440.xyz",
- "takeit441.xyz",
- "takeit442.xyz",
- "takeit443.xyz",
- "takeit444.xyz",
- "takeit445.xyz",
- "takeit446.xyz",
- "takeit447.xyz",
- "takeit448.xyz",
- "takeit449.xyz",
- "takeit45.xyz",
- "takeit450.xyz",
- "takeit451.xyz",
- "takeit452.xyz",
- "takeit453.xyz",
- "takeit454.xyz",
- "takeit455.xyz",
- "takeit456.xyz",
- "takeit457.xyz",
- "takeit458.xyz",
- "takeit459.xyz",
- "takeit46.xyz",
- "takeit460.xyz",
- "takeit461.xyz",
- "takeit462.xyz",
- "takeit463.xyz",
- "takeit464.xyz",
- "takeit465.xyz",
- "takeit466.xyz",
- "takeit467.xyz",
- "takeit468.xyz",
- "takeit469.xyz",
- "takeit47.xyz",
- "takeit470.xyz",
- "takeit471.xyz",
- "takeit472.xyz",
- "takeit473.xyz",
- "takeit474.xyz",
- "takeit475.xyz",
- "takeit476.xyz",
- "takeit477.xyz",
- "takeit478.xyz",
- "takeit479.xyz",
- "takeit48.xyz",
- "takeit480.xyz",
- "takeit481.xyz",
- "takeit482.xyz",
- "takeit483.xyz",
- "takeit484.xyz",
- "takeit485.xyz",
- "takeit486.xyz",
- "takeit487.xyz",
- "takeit488.xyz",
- "takeit489.xyz",
- "takeit49.xyz",
- "takeit490.xyz",
- "takeit491.xyz",
- "takeit492.xyz",
- "takeit493.xyz",
- "takeit494.xyz",
- "takeit495.xyz",
- "takeit496.xyz",
- "takeit497.xyz",
- "takeit498.xyz",
- "takeit499.xyz",
- "takeit50.xyz",
- "takeit500.xyz",
- "takeit501.xyz",
- "takeit502.xyz",
- "takeit503.xyz",
- "takeit504.xyz",
- "takeit505.xyz",
- "takeit506.xyz",
- "takeit507.xyz",
- "takeit508.xyz",
- "takeit509.xyz",
- "takeit51.xyz",
- "takeit510.xyz",
- "takeit511.xyz",
- "takeit512.xyz",
- "takeit513.xyz",
- "takeit514.xyz",
- "takeit515.xyz",
- "takeit516.xyz",
- "takeit517.xyz",
- "takeit518.xyz",
- "takeit519.xyz",
- "takeit520.xyz",
- "takeit521.xyz",
- "takeit522.xyz",
- "takeit523.xyz",
- "takeit524.xyz",
- "takeit525.xyz",
- "takeit526.xyz",
- "takeit527.xyz",
- "takeit528.xyz",
- "takeit529.xyz",
- "takeit53.xyz",
- "takeit530.xyz",
- "takeit531.xyz",
- "takeit533.xyz",
- "takeit534.xyz",
- "takeit535.xyz",
- "takeit536.xyz",
- "takeit537.xyz",
- "takeit538.xyz",
- "takeit539.xyz",
- "takeit54.xyz",
- "takeit540.xyz",
- "takeit541.xyz",
- "takeit542.xyz",
- "takeit543.xyz",
- "takeit544.xyz",
- "takeit545.xyz",
- "takeit546.xyz",
- "takeit547.xyz",
- "takeit548.xyz",
- "takeit549.xyz",
- "takeit55.xyz",
- "takeit550.xyz",
- "takeit551.xyz",
- "takeit552.xyz",
- "takeit553.xyz",
- "takeit554.xyz",
- "takeit555.xyz",
- "takeit556.xyz",
- "takeit557.xyz",
- "takeit558.xyz",
- "takeit559.xyz",
- "takeit56.xyz",
- "takeit560.xyz",
- "takeit561.xyz",
- "takeit562.xyz",
- "takeit563.xyz",
- "takeit564.xyz",
- "takeit565.xyz",
- "takeit566.xyz",
- "takeit567.xyz",
- "takeit568.xyz",
- "takeit569.xyz",
- "takeit57.xyz",
- "takeit570.xyz",
- "takeit571.xyz",
- "takeit572.xyz",
- "takeit573.xyz",
- "takeit574.xyz",
- "takeit575.xyz",
- "takeit576.xyz",
- "takeit577.xyz",
- "takeit578.xyz",
- "takeit579.xyz",
- "takeit58.xyz",
- "takeit580.xyz",
- "takeit581.xyz",
- "takeit582.xyz",
- "takeit583.xyz",
- "takeit584.xyz",
- "takeit586.xyz",
- "takeit587.xyz",
- "takeit588.xyz",
- "takeit589.xyz",
- "takeit59.xyz",
- "takeit590.xyz",
- "takeit591.xyz",
- "takeit592.xyz",
- "takeit594.xyz",
- "takeit596.xyz",
- "takeit597.xyz",
- "takeit598.xyz",
- "takeit599.xyz",
- "takeit60.xyz",
- "takeit601.xyz",
- "takeit602.xyz",
- "takeit603.xyz",
- "takeit604.xyz",
- "takeit605.xyz",
- "takeit606.xyz",
- "takeit607.xyz",
- "takeit608.xyz",
- "takeit61.xyz",
- "takeit610.xyz",
- "takeit611.xyz",
- "takeit612.xyz",
- "takeit613.xyz",
- "takeit614.xyz",
- "takeit615.xyz",
- "takeit616.xyz",
- "takeit617.xyz",
- "takeit618.xyz",
- "takeit619.xyz",
- "takeit62.xyz",
- "takeit620.xyz",
- "takeit621.xyz",
- "takeit622.xyz",
- "takeit623.xyz",
- "takeit624.xyz",
- "takeit625.xyz",
- "takeit626.xyz",
- "takeit627.xyz",
- "takeit628.xyz",
- "takeit629.xyz",
- "takeit63.xyz",
- "takeit630.xyz",
- "takeit631.xyz",
- "takeit632.xyz",
- "takeit633.xyz",
- "takeit634.xyz",
- "takeit635.xyz",
- "takeit636.xyz",
- "takeit637.xyz",
- "takeit638.xyz",
- "takeit639.xyz",
- "takeit64.xyz",
- "takeit640.xyz",
- "takeit641.xyz",
- "takeit642.xyz",
- "takeit643.xyz",
- "takeit644.xyz",
- "takeit645.xyz",
- "takeit646.xyz",
- "takeit647.xyz",
- "takeit648.xyz",
- "takeit649.xyz",
- "takeit650.xyz",
- "takeit651.xyz",
- "takeit652.xyz",
- "takeit653.xyz",
- "takeit654.xyz",
- "takeit655.xyz",
- "takeit656.xyz",
- "takeit657.xyz",
- "takeit658.xyz",
- "takeit659.xyz",
- "takeit66.xyz",
- "takeit660.xyz",
- "takeit661.xyz",
- "takeit662.xyz",
- "takeit67.xyz",
- "takeit68.xyz",
- "takeit69.xyz",
- "takeit70.xyz",
- "takeit71.xyz",
- "takeit72.xyz",
- "takeit73.xyz",
- "takeit74.xyz",
- "takeit75.xyz",
- "takeit76.xyz",
- "takeit77.xyz",
- "takeit78.xyz",
- "takeit79.xyz",
- "takeit80.xyz",
- "takeit81.xyz",
- "takeit82.xyz",
- "takeit83.xyz",
- "takeit84.xyz",
- "takeit85.xyz",
- "takeit86.xyz",
- "takeit87.xyz",
- "takeit88.xyz",
- "takeit89.xyz",
- "takeit90.xyz",
- "takeit91.xyz",
- "takeit92.xyz",
- "takeit93.xyz",
- "takeit94.xyz",
- "takeit95.xyz",
- "takeit96.xyz",
- "takeit97.xyz",
- "takeit98.xyz",
- "takeit99.xyz",
- "tasty-drop.pp.ua",
- "tasty-skill.net.ru",
- "tastygo.ru.com",
- "tastyskill.net.ru",
- "taty-dropp.info",
- "team-dream.xyz",
- "team.the-shrubbery.co.uk",
- "teamastrallis.org.ru",
- "teamfnat.net.ru",
- "teamfnattic.org.ru",
- "teamgog.pp.ua",
- "terrifvvev.com",
- "test-domuin2.com",
- "test-domuin3.ru",
- "test-domuin4.ru",
- "test-domuin5.ru",
- "testbot2021.ru",
- "testy-drop.pp.ua",
- "tf2market.store",
- "thediscordapp.com",
- "themekaversed.org",
- "themekaverses.org",
- "think-when.xyz",
- "thor-case.net.ru",
- "threemeterssky.ru",
- "tigers.pp.ua",
- "tik-team-topp.org.ru",
- "tiktok.verifiedbadgehelp-form.ml",
- "tiktokmagic.ru",
- "tiktoksupport.ru.com",
- "tini.best",
- "tipteamgg.xyz",
- "toolprotimenow.com",
- "toom-skins.xyz",
- "toornirs.pp.ua",
- "top-team.org.ru",
- "topcase.monster",
- "topconsumerproductsonline.com",
- "topeasyllucky.pp.ua",
- "topgadgetneckmassager.com",
- "toprobux.site",
- "topstteeamleto2021.net.ru",
- "topsweeps.com",
- "topvincere.net.ru",
- "topvincere.org.ru",
- "topvincere.pp.ru",
- "topw-gamez.xyz",
- "topz-games.xyz",
- "tourggesports.ru",
- "tournament.ru.com",
- "tournamentcs.live",
- "tournamentcsgo.ga",
- "tournamentcsgo.gq",
- "tournaments.ru.com",
- "tournamentsplay.site",
- "tournamentt.com",
- "tournrecruit.xyz",
- "trabeoffer.ru",
- "trabeoffers.xyz",
- "trade-csmoney.ru",
- "trade-dexter.xyz",
- "trade-leagues.com",
- "trade-link-offer.ru",
- "trade-linkk.ru",
- "trade-offers.link",
- "trade-offersz.pp.ua",
- "trade-profile.fun",
- "trade.ru.com",
- "tradeaffix.pp.ua",
- "tradeandyou.ru",
- "tradecs.ru.com",
- "tradelink.live",
- "tradeoff.space",
- "tradeoffer-link.ru.com",
- "tradeoffer-new.ru",
- "tradeoffer.com.ru",
- "tradeoffers.net.ru",
- "tradeoffers11.xyz",
- "traderlink.ru.com",
- "traders-offers.com",
- "trades-league.com",
- "trades-offers.xyz",
- "tradesoffers.com",
- "treader-offer.com",
- "tredecsgo.com",
- "treders-offers.com",
- "treplov.pp.ua",
- "triumph.tk",
- "true-money.xyz",
- "truepnl-giveaway.info",
- "trustpool.xyz",
- "tryinfinitikloud.com",
- "tryultrassenceskin.com",
- "tugceyumakogullari.tk",
- "twitch-facepanch.com",
- "twitch-nude.com",
- "twitch-starter.com",
- "twitch.facepunch-llc.com",
- "twitch.facepunch-ltd.com",
- "twitch.facepunchs.com",
- "twitch.facepunchstudio.com",
- "twitch.rust-ltd.com",
- "tylofpcasy.xyz",
- "u924157p.beget.tech",
- "ultimateskins.xyz",
- "ultracup.fun",
- "umosleep.ru",
- "universityteam.xyz",
- "up-discord.ru",
- "up-nitro.com",
- "up-you.ru",
- "upcs.monster",
- "us-appmonie.yousweeps.com",
- "uspringcup.com",
- "ut.ntwrk.yunihost.ru",
- "v-roblox.com",
- "vbucksminer.ru",
- "verifapp.us",
- "verification-discord.com",
- "verifications-discord.com",
- "verifiedbadgehelp-form.ml",
- "verify-discord.com",
- "verifyaccount-for-bluetick.com",
- "versus-cup.ru",
- "versus-play.ru",
- "versuscs.ru",
- "versuscsgoplay.pp.ua",
- "versusplay.ru",
- "vippobrit.ru",
- "vippobrit1.ru.com",
- "visaxsteam.ru",
- "vitality-cyber.net",
- "vitality-playtime.com",
- "vitality-top.ru",
- "vitalityboxs.com",
- "vitalitycamp.ru",
- "vitalityesports.net",
- "vitalitygg.ru",
- "viwwzagul.xyz",
- "viwwzaguls.xyz",
- "viwwzagulw.xyz",
- "viwwzaguly.xyz",
- "vkbonus.club",
- "vm1189661.firstbyte.club",
- "vpitems.xyz",
- "vqojiorq.ru",
- "waccupzero.ru.com",
- "waccupzerow.monster",
- "wallet-steam.ml",
- "wanmei-hy.ru",
- "wanmeics6.ru",
- "wanmeicsgo1.ru",
- "wanmeipt.ru",
- "wanmeizi.ru",
- "waterbets.ru",
- "waucupsz.monster",
- "wavebtc.com",
- "we-player.ru",
- "wearewinagain.xyz",
- "webr-roblox.com",
- "weplay.ru.com",
- "were-want.ru.com",
- "wheel-run.ru",
- "white-guns.xyz",
- "white-list.live",
- "whitelampa.xyz",
- "widesdays.com",
- "win-lems.org.ru",
- "win-skin.top",
- "win-skin.xyz",
- "win-trader.org.ru",
- "winknifespin.xyz",
- "winner-roll.ru",
- "winrbx1s1.pw",
- "wins-navi.com",
- "winskin-simple.xyz",
- "winskins.top",
- "wintheskin.xyz",
- "withereum.com",
- "word-the.xyz",
- "wowfnatic.ru",
- "wtf-magic.ru",
- "wtf-magic.top",
- "wtf-magicru.top",
- "wtf-win.net.ru",
- "ww1.dicsordapp.com",
- "ww1.discordapp.org",
- "ww11.steamcommunity.download",
- "ww16.discordcanary.com",
- "ww8.steamcommmunity.ru.com",
- "wwdiscord.com",
- "www-steamcommunlty.com",
- "www2.c2bit.online",
- "wwwlog-in.xyz",
- "wyxy.ru",
- "x33681t2.beget.tech",
- "xdiscord.com",
- "xesa-nitro.com",
- "xess-nitro.com",
- "xfxcheats.online",
- "xgamercup.com",
- "xn--e1agajgahgxri7a.site",
- "xn--steamcommunit-ge3g.com",
- "xorialloy.xyz",
- "xpro.gift",
- "xpro.ws",
- "xpromo-discord.com",
- "xroll.space",
- "xscsgo.com",
- "xtradefox.com",
- "xtradeskin.com",
- "yeppymoll.xyz",
- "yolock.site",
- "youtubers2021.xyz",
- "youtubersrwrds.xyz",
- "yummy-nitro.com",
- "z93729n9.beget.tech",
- "zakat.ntwrk.yunihost.ru",
- "zerocup.ru",
- "zipsetgo.com",
- "zonewarco.org.ru",
- "zonewarco.org.ru",
- // "steamcommunity.co",
-];
diff --git a/src/lib/badwords.ts b/src/lib/badwords.ts
deleted file mode 100644
index feb74cb..0000000
--- a/src/lib/badwords.ts
+++ /dev/null
@@ -1,752 +0,0 @@
-import type { BadWords } from "./common/AutoMod.js";
-
-const enum Severity {
- DELETE,
- WARN,
- TEMP_MUTE,
- PERM_MUTE,
-}
-
-export default {
- /* -------------------------------------------------------------------------- */
- /* Slurs */
- /* -------------------------------------------------------------------------- */
- "Slurs": [
- {
- match: "faggot",
- severity: Severity.TEMP_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "homophobic slur",
- regex: false,
- },
- {
- match: "nigga",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "racial slur",
- regex: false,
- },
- {
- match: "nigger",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "racial slur",
- regex: false,
- },
- {
- match: "nigra",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: false,
- ignoreCapitalization: true,
- reason: "racial slur",
- regex: false,
- },
- {
- match: "retard",
- severity: Severity.TEMP_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "ableist slur",
- regex: false,
- },
- {
- match: "retarted",
- severity: Severity.TEMP_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "ableist slur",
- regex: false,
- },
- {
- match: "slut",
- severity: Severity.WARN,
- ignoreSpaces: false,
- ignoreCapitalization: true,
- reason: "derogatory term",
- regex: false,
- },
- {
- match: "tar baby",
- severity: Severity.TEMP_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "racial slur",
- regex: false,
- },
- {
- match: "whore",
- severity: Severity.WARN,
- ignoreSpaces: false,
- ignoreCapitalization: true,
- reason: "derogatory term",
- regex: false,
- },
- {
- match: "卍",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "racist symbol",
- regex: false,
- },
- {
- //? N word
- match: "space movie 1992",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "racial slur",
- regex: false,
- },
- {
- //? N word
- match: "黑鬼",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "racial slur",
- regex: false,
- },
- ],
-
- /* -------------------------------------------------------------------------- */
- /* Steam Scams */
- /* -------------------------------------------------------------------------- */
- "Steam Scams": [
- {
- //? I'm on tilt, in the cop they gave the status "Unreliable"
- match: 'Я в тильте, в кс дали статус "Ненадежный"',
- severity: Severity.WARN,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "steam scam phrase",
- regex: false,
- },
- {
- match: "hello i am leaving cs:go",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "steam scam phrase",
- regex: false,
- },
- {
- match: "hello! I'm done with csgo",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "steam scam phrase",
- regex: false,
- },
- {
- match: "hi bro, i'm leaving this fucking game, take my skin",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "steam scam phrase",
- regex: false,
- },
- {
- match: "hi friend, today i am leaving this fucking game",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "steam scam phrase",
- regex: false,
- },
- {
- match: "hi guys, i'm leaving this fucking game, take my",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "steam scam phrase",
- regex: false,
- },
- {
- match: "hi, bro h am leaving cs:go and giving away my skin",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "steam scam phrase",
- regex: false,
- },
- {
- match: "hi, bro i am leaving cs:go and giving away my skin",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "steam scam phrase",
- regex: false,
- },
- {
- match: "i confirm all exchanges, there won't be enough",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "steam scam phrase",
- regex: false,
- },
- {
- match: "i quit csgo",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "steam scam phrase",
- regex: false,
- },
- {
- match: "the first three who send a trade",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "steam scam phrase",
- regex: false,
- },
- {
- match: "you can choose any skin for yourself",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "steam scam phrase",
- regex: false,
- },
- {
- match: "Hey, I'm leaving for the army and giving the skins",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "steam scam phrase",
- regex: false,
- },
- {
- match: "fuck this trash called CS:GO, deleted,",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "steam scam phrase",
- regex: false,
- },
- {
- match: "please take my skins",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "steam scam phrase",
- regex: false,
- },
- {
- match: "Hi, I stopped playing CS:GO and decided to giveaway my inventory.",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "steam scam phrase",
- regex: false,
- },
- ],
-
- /* -------------------------------------------------------------------------- */
- /* Nitro Scams */
- /* -------------------------------------------------------------------------- */
- "Nitro Scams": [
- {
- match: "and there is discord hallween's giveaway",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "discord nitro for free - steam store",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "free 3 months of discord nitro",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "free discord nitro airdrop",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "get 3 months of discord nitro",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "get discord nitro for free",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "get free discord nitro from steam",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "lol, jahjajha free discord nitro for 3 month!!",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "steam is giving away 3 months of discord nitro for free to all no limited steam users",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- //? Lol, 1 month free discord nitro!
- match: "Лол, бесплатный дискорд нитро на 1 месяц!",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Airdrop Discord FREE NITRO from Steam —",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "take nitro faster, it's already running out",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: false,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "only the first 10 people will have time to take nitro",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: false,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Discord is giving away nitro!",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: false,
- ignoreCapitalization: false,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Free gift discord nitro for 1 month!",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: false,
- ignoreCapitalization: false,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Hi i claim this nitro for free 3 months lol!",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "bro watch this, working nitro gen",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: false,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Free distribution of discord nitro for 3 months from steam!",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Get 3 Months of Discord Nitro. Personalize your profile, screen share in HD, upgrade your emojis, and more!",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Steam is giving away free discord nitro, have time to pick up at my link",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Airdrop Discord NITRO with",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Check this lol, there nitro is handed out for free, take it until everything is sorted out",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "A free Discord Nitro | Steam Store Discord Nitro Distribution.",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Xbox gives away discord nitro for free",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "airdrop discord nitro by steam",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- //? 3 months nitro free from steam, take too
- match: "3 месяца нитро бесплатно от стима, забирайте тоже",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Free distributiοn of discοrd nitrο for 3 months from steаm!",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Free discord nitro for 1 month!",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "I got some nitro left over here",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Hey, steam gived nitro",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "nitro giveaway by steam, take it",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "3 months nitro from styme,",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "XBOX and DISCORD are giving away free NITRO FULL for a month.",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Hi,take the Discord Nitro for free",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- //? Discord nitro got free, take it before it's too late
- match: "Дискорд нитро получил бесплатно,забирай пока не поздно",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "1 month nitro for free",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Gifts for the new year, nitro for 3 months",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "1 month nitro from steam, take it guys",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Hello, discord and steam are giving away nitro, take it away",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Who is first? :)",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Whо is first? :)",
- //? This one uses a different o, prob should make some autodelete if includes link and special char
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Discord Nitro distribution from STEAM",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "3 month nitro for free, take it ",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "3 months nitro from steam, take it guys)",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Gifts from steam nitro, gifts for 3 months",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Free subscription for 3 months DISCORD NITRO",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "who will catch this gift?)",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "take it guys :)",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Discord and Steam are giving away a free 3-month Discord Gift subscription!",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- {
- match: "Discord free nitro from steam",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "discord nitro scam phrase",
- regex: false,
- },
- ],
-
- /* -------------------------------------------------------------------------- */
- /* Misc Scams */
- /* -------------------------------------------------------------------------- */
- "Misc Scams": [
- {
- match: "found a cool software that improves the",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "misc. scam phrase",
- regex: false,
- },
- {
- match:
- "there is a possible chance tomorrow there will be a cyber-attack event where on all social networks including Discord there will be people trying",
- severity: Severity.WARN,
- ignoreSpaces: false,
- ignoreCapitalization: true,
- reason: "annoying copy pasta",
- regex: false,
- },
- {
- match: "i made a game can you test play ?",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "malware phrase",
- regex: false,
- },
- {
- match: "tell me if something is wrong in the game",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "malware phrase",
- regex: false,
- },
- {
- match: "Hi, can you check out the game I created today:)",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "malware phrase",
- regex: false,
- },
- {
- match: "Just want to get other people's opinions, what to add and what to remove.",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "malware phrase",
- regex: false,
- },
- {
- match: "https://discord.gg/KKnGGvEPVM",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "misc. scam phrase",
- regex: false,
- },
- {
- match: "https://discord.gg/rykjvpTGrB",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "misc. scam phrase",
- regex: false,
- },
- {
- match: "https://discord.gg/XTDQgJ9YMp",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "misc. scam phrase",
- regex: false,
- },
- ],
-
- /* -------------------------------------------------------------------------- */
- /* Advertising */
- /* -------------------------------------------------------------------------- */
- "Advertising": [
- {
- match: "😀 wow only 13+... 😳 are allowed to see my about me 😏",
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: "advertising",
- regex: false,
- },
- ],
-} as BadWords;
diff --git a/src/lib/common/AutoMod.ts b/src/lib/common/AutoMod.ts
deleted file mode 100644
index 44c6dee..0000000
--- a/src/lib/common/AutoMod.ts
+++ /dev/null
@@ -1,529 +0,0 @@
-import { colors, emojis, format, formatError, Moderation, unmuteResponse } from '#lib';
-import assert from 'assert/strict';
-import chalk from 'chalk';
-import {
- ActionRowBuilder,
- ButtonBuilder,
- ButtonStyle,
- EmbedBuilder,
- GuildMember,
- PermissionFlagsBits,
- type ButtonInteraction,
- type Message,
- type Snowflake,
- type TextChannel
-} from 'discord.js';
-import UnmuteCommand from '../../commands/moderation/unmute.js';
-
-/**
- * Handles auto moderation functionality.
- */
-export class AutoMod {
- /**
- * Whether or not a punishment has already been given to the user
- */
- private punished = false;
-
- /**
- * @param message The message to check and potentially perform automod actions to
- */
- public constructor(private message: Message) {
- if (message.author.id === message.client.user?.id) return;
- void this.handle();
- }
-
- /**
- * Whether or not the message author is immune to auto moderation
- */
- private get isImmune() {
- if (!this.message.inGuild()) return false;
- assert(this.message.member);
-
- if (this.message.author.isOwner()) return true;
- if (this.message.guild.ownerId === this.message.author.id) return true;
- if (this.message.member.permissions.has('Administrator')) return true;
-
- return false;
- }
-
- /**
- * Handles the auto moderation
- */
- private async handle() {
- if (!this.message.inGuild()) return;
- if (!(await this.message.guild.hasFeature('automod'))) return;
- if (this.message.author.bot) return;
-
- traditional: {
- if (this.isImmune) break traditional;
- const badLinksArray = this.message.client.utils.getShared('badLinks');
- const badLinksSecretArray = this.message.client.utils.getShared('badLinksSecret');
- const badWordsRaw = this.message.client.utils.getShared('badWords');
-
- const customAutomodPhrases = (await this.message.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
- }));
-
- const parsedBadWords = Object.values(badWordsRaw).flat();
-
- const result = [
- ...this.checkWords(customAutomodPhrases),
- ...this.checkWords((await this.message.guild.hasFeature('excludeDefaultAutomod')) ? [] : parsedBadWords),
- ...this.checkWords((await this.message.guild.hasFeature('excludeAutomodScamLinks')) ? [] : badLinks)
- ];
-
- if (result.length === 0) break traditional;
-
- const highestOffence = result.sort((a, b) => b.severity - a.severity)[0];
-
- if (highestOffence.severity === undefined || highestOffence.severity === null) {
- void this.message.guild.sendLogChannel('error', {
- embeds: [
- {
- title: 'AutoMod Error',
- description: `Unable to find severity information for ${format.inlineCode(highestOffence.match)}`,
- color: colors.error
- }
- ]
- });
- } else {
- const color = this.punish(highestOffence);
- void this.log(highestOffence, color, result);
- }
- }
-
- other: {
- if (this.isImmune) break other;
- if (!this.punished && (await this.message.guild.hasFeature('delScamMentions'))) void this.checkScamMentions();
- }
-
- if (!this.punished && (await this.message.guild.hasFeature('perspectiveApi'))) void this.checkPerspectiveApi();
- }
-
- /**
- * 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
- */
- private checkWords(words: BadWordDetails[]): 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(this.message.content, word).includes(this.format(word.match, word))) {
- matchedWords.push(word);
- }
- }
- }
- return matchedWords;
- }
-
- /**
- * If the message contains '@everyone' or '@here' and it contains a common scam phrase, it will be deleted
- * @returns
- */
- private 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.message.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.punish({ severity: Severity.TEMP_MUTE, reason: 'everyone mention and scam phrase' } as BadWordDetails);
- void this.message.guild!.sendLogChannel('automod', {
- embeds: [
- new EmbedBuilder()
- .setTitle(`[Severity ${Severity.TEMP_MUTE}] Mention Scam Deleted`)
- .setDescription(
- `**User:** ${this.message.author} (${this.message.author.tag})\n**Sent From:** <#${this.message.channel.id}> [Jump to context](${this.message.url})`
- )
- .addFields({
- name: 'Message Content',
- value: `${await this.message.client.utils.codeblock(this.message.content, 1024)}`
- })
- .setColor(color)
- .setTimestamp()
- ],
- components: [this.buttons(this.message.author.id, 'everyone mention and scam phrase')]
- });
- }
- }
-
- private async checkPerspectiveApi() {
- return;
- if (!this.message.client.config.isDevelopment) return;
-
- if (!this.message.content) return;
- this.message.client.perspective.comments.analyze(
- {
- key: this.message.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)));
- }
- );
- }
-
- /**
- * 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
- */
- private format(string: string, wordOptions: BadWordDetails) {
- const temp = wordOptions.ignoreCapitalization ? string.toLowerCase() : string;
- return wordOptions.ignoreSpaces ? temp.replace(/ /g, '') : temp;
- }
-
- /**
- * Punishes the user based on the severity of the offense
- * @param highestOffence 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
- */
- private punish(highestOffence: BadWordDetails) {
- let color;
- switch (highestOffence.severity) {
- case Severity.DELETE: {
- color = colors.lightGray;
- void this.message.delete().catch((e) => deleteError.bind(this, e));
- this.punished = true;
- break;
- }
- case Severity.WARN: {
- color = colors.yellow;
- void this.message.delete().catch((e) => deleteError.bind(this, e));
- void this.message.member?.bushWarn({
- moderator: this.message.guild!.members.me!,
- reason: `[AutoMod] ${highestOffence.reason}`
- });
- this.punished = true;
- break;
- }
- case Severity.TEMP_MUTE: {
- color = colors.orange;
- void this.message.delete().catch((e) => deleteError.bind(this, e));
- void this.message.member?.bushMute({
- moderator: this.message.guild!.members.me!,
- reason: `[AutoMod] ${highestOffence.reason}`,
- duration: 900_000 // 15 minutes
- });
- this.punished = true;
- break;
- }
- case Severity.PERM_MUTE: {
- color = colors.red;
- void this.message.delete().catch((e) => deleteError.bind(this, e));
- void this.message.member?.bushMute({
- moderator: this.message.guild!.members.me!,
- reason: `[AutoMod] ${highestOffence.reason}`,
- duration: 0 // permanent
- });
- this.punished = true;
- break;
- }
- default: {
- throw new Error(`Invalid severity: ${highestOffence.severity}`);
- }
- }
-
- return color;
-
- async function deleteError(this: AutoMod, e: Error | any) {
- void this.message.guild?.sendLogChannel('error', {
- embeds: [
- {
- title: 'AutoMod Error',
- description: `Unable to delete triggered message.`,
- fields: [{ name: 'Error', value: await this.message.client.utils.codeblock(`${formatError(e)}`, 1024, 'js', true) }],
- color: colors.error
- }
- ]
- });
- }
- }
-
- /**
- * Log an automod infraction to the guild's specified automod log channel
- * @param highestOffence The highest severity word found in the message
- * @param color The color that the log embed should be (based on the severity)
- * @param offenses The other offenses that were also matched in the message
- */
- private async log(highestOffence: BadWordDetails, color: number, offenses: BadWordDetails[]) {
- void this.message.client.console.info(
- 'autoMod',
- `Severity <<${highestOffence.severity}>> action performed on <<${this.message.author.tag}>> (<<${
- this.message.author.id
- }>>) in <<#${(this.message.channel as TextChannel).name}>> in <<${this.message.guild!.name}>>`
- );
-
- await this.message.guild!.sendLogChannel('automod', {
- embeds: [
- new EmbedBuilder()
- .setTitle(`[Severity ${highestOffence.severity}] Automod Action Performed`)
- .setDescription(
- `**User:** ${this.message.author} (${this.message.author.tag})\n**Sent From:** <#${
- this.message.channel.id
- }> [Jump to context](${this.message.url})\n**Blacklisted Words:** ${offenses.map((o) => `\`${o.match}\``).join(', ')}`
- )
- .addFields({
- name: 'Message Content',
- value: `${await this.message.client.utils.codeblock(this.message.content, 1024)}`
- })
- .setColor(color)
- .setTimestamp()
- .setAuthor({ name: this.message.author.tag, url: this.message.author.displayAvatarURL() })
- ],
- components: highestOffence.severity >= 2 ? [this.buttons(this.message.author.id, highestOffence.reason)] : undefined
- });
- }
-
- private buttons(userId: Snowflake, reason: string): ActionRowBuilder<ButtonBuilder> {
- return new ActionRowBuilder<ButtonBuilder>().addComponents(
- new ButtonBuilder({
- style: ButtonStyle.Danger,
- label: 'Ban User',
- customId: `automod;ban;${userId};${reason}`
- }),
- new ButtonBuilder({
- style: ButtonStyle.Success,
- label: 'Unmute User',
- customId: `automod;unmute;${userId}`
- })
- );
- }
-
- /**
- * Handles the ban button in the automod log.
- * @param interaction The button interaction.
- */
- public static async handleInteraction(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
- */
- regex: boolean;
-}
-
-/**
- * Blacklisted words mapped to their details
- */
-export interface BadWords {
- [category: string]: BadWordDetails[];
-}
diff --git a/src/lib/common/ButtonPaginator.ts b/src/lib/common/ButtonPaginator.ts
deleted file mode 100644
index 02c78ea..0000000
--- a/src/lib/common/ButtonPaginator.ts
+++ /dev/null
@@ -1,219 +0,0 @@
-import { DeleteButton, type CommandMessage, type SlashMessage } from '#lib';
-import { CommandUtil } from 'discord-akairo';
-import {
- ActionRowBuilder,
- ButtonBuilder,
- ButtonStyle,
- EmbedBuilder,
- type APIEmbed,
- type Message,
- type MessageComponentInteraction
-} from 'discord.js';
-
-/**
- * Sends multiple embeds with controls to switch between them
- */
-export class ButtonPaginator {
- /**
- * The current page of the paginator
- */
- protected curPage: number;
-
- /**
- * The paginator message
- */
- protected sentMessage: Message | undefined;
-
- /**
- * @param message The message that triggered the command
- * @param embeds The embeds to switch between
- * @param text The optional text to send with the paginator
- * @param {} [deleteOnExit=true] Whether the paginator message gets deleted when the exit button is pressed
- * @param startOn The page to start from (**not** the index)
- */
- protected constructor(
- protected message: CommandMessage | SlashMessage,
- protected embeds: EmbedBuilder[] | APIEmbed[],
- protected text: string | null,
- protected deleteOnExit: boolean,
- startOn: number
- ) {
- this.curPage = startOn - 1;
-
- // add footers
- for (let i = 0; i < embeds.length; i++) {
- if (embeds[i] instanceof EmbedBuilder) {
- (embeds[i] as EmbedBuilder).setFooter({ text: `Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}` });
- } else {
- (embeds[i] as APIEmbed).footer = {
- text: `Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}`
- };
- }
- }
- }
-
- /**
- * The number of pages in the paginator
- */
- protected get numPages(): number {
- return this.embeds.length;
- }
-
- /**
- * Sends the paginator message
- */
- protected async send() {
- this.sentMessage = await this.message.util.reply({
- content: this.text,
- embeds: [this.embeds[this.curPage]],
- components: [this.getPaginationRow()]
- });
-
- const collector = this.sentMessage.createMessageComponentCollector({
- filter: (i) => i.customId.startsWith('paginate_'),
- time: 300_000
- });
- collector.on('collect', (i) => void this.collect(i));
- collector.on('end', () => void this.end());
- }
-
- /**
- * Handles interactions with the paginator
- * @param interaction The interaction received
- */
- protected async collect(interaction: MessageComponentInteraction) {
- if (interaction.user.id !== this.message.author.id && !this.message.client.config.owners.includes(interaction.user.id))
- return await interaction?.deferUpdate().catch(() => null);
-
- switch (interaction.customId) {
- case 'paginate_beginning':
- this.curPage = 0;
- await this.edit(interaction);
- break;
- case 'paginate_back':
- this.curPage--;
- await this.edit(interaction);
- break;
- case 'paginate_stop':
- if (this.deleteOnExit) {
- await interaction.deferUpdate().catch(() => null);
- await this.sentMessage!.delete().catch(() => null);
- break;
- } else {
- await interaction
- ?.update({
- content: `${this.text ? `${this.text}\n` : ''}Command closed by user.`,
- embeds: [],
- components: []
- })
- .catch(() => null);
- break;
- }
- case 'paginate_next':
- this.curPage++;
- await this.edit(interaction);
- break;
- case 'paginate_end':
- this.curPage = this.embeds.length - 1;
- await this.edit(interaction);
- break;
- }
- }
-
- /**
- * Ends the paginator
- */
- protected async end() {
- if (this.sentMessage && !CommandUtil.deletedMessages.has(this.sentMessage.id))
- await this.sentMessage
- .edit({
- content: this.text,
- embeds: [this.embeds[this.curPage]],
- components: [this.getPaginationRow(true)]
- })
- .catch(() => null);
- }
-
- /**
- * Edits the paginator message
- * @param interaction The interaction received
- */
- protected async edit(interaction: MessageComponentInteraction) {
- await interaction
- ?.update({
- content: this.text,
- embeds: [this.embeds[this.curPage]],
- components: [this.getPaginationRow()]
- })
- .catch(() => null);
- }
-
- /**
- * Generates the pagination row based on the class properties
- * @param disableAll Whether to disable all buttons
- * @returns The generated {@link ActionRow}
- */
- protected getPaginationRow(disableAll = false) {
- return new ActionRowBuilder<ButtonBuilder>().addComponents(
- new ButtonBuilder({
- style: ButtonStyle.Primary,
- customId: 'paginate_beginning',
- emoji: PaginateEmojis.BEGINNING,
- disabled: disableAll || this.curPage === 0
- }),
- new ButtonBuilder({
- style: ButtonStyle.Primary,
- customId: 'paginate_back',
- emoji: PaginateEmojis.BACK,
- disabled: disableAll || this.curPage === 0
- }),
- new ButtonBuilder({
- style: ButtonStyle.Primary,
- customId: 'paginate_stop',
- emoji: PaginateEmojis.STOP,
- disabled: disableAll
- }),
- new ButtonBuilder({
- style: ButtonStyle.Primary,
- customId: 'paginate_next',
- emoji: PaginateEmojis.FORWARD,
- disabled: disableAll || this.curPage === this.numPages - 1
- }),
- new ButtonBuilder({
- style: ButtonStyle.Primary,
- customId: 'paginate_end',
- emoji: PaginateEmojis.END,
- disabled: disableAll || this.curPage === this.numPages - 1
- })
- );
- }
-
- /**
- * Sends multiple embeds with controls to switch between them
- * @param message The message to respond to
- * @param embeds The embeds to switch between
- * @param text The text send with the embeds (optional)
- * @param deleteOnExit Whether to delete the message when the exit button is clicked (defaults to true)
- * @param startOn The page to start from (**not** the index)
- */
- public static async send(
- message: CommandMessage | SlashMessage,
- embeds: EmbedBuilder[] | APIEmbed[],
- text: string | null = null,
- deleteOnExit = true,
- startOn = 1
- ) {
- // no need to paginate if there is only one page
- if (embeds.length === 1) return DeleteButton.send(message, { embeds: embeds });
-
- return await new ButtonPaginator(message, embeds, text, deleteOnExit, startOn).send();
- }
-}
-
-export const PaginateEmojis = {
- BEGINNING: { id: '853667381335162910', name: 'w_paginate_beginning', animated: false } as const,
- BACK: { id: '853667410203770881', name: 'w_paginate_back', animated: false } as const,
- STOP: { id: '853667471110570034', name: 'w_paginate_stop', animated: false } as const,
- FORWARD: { id: '853667492680564747', name: 'w_paginate_next', animated: false } as const,
- END: { id: '853667514915225640', name: 'w_paginate_end', animated: false } as const
-} as const;
diff --git a/src/lib/common/ConfirmationPrompt.ts b/src/lib/common/ConfirmationPrompt.ts
deleted file mode 100644
index b87d9ef..0000000
--- a/src/lib/common/ConfirmationPrompt.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { type CommandMessage, type SlashMessage } from '#lib';
-import { ActionRowBuilder, ButtonBuilder, ButtonStyle, type MessageComponentInteraction, type MessageOptions } from 'discord.js';
-
-/**
- * Sends a message with buttons for the user to confirm or cancel the action.
- */
-export class ConfirmationPrompt {
- /**
- * @param message The message that triggered the command
- * @param messageOptions Options for sending the message
- */
- protected constructor(protected message: CommandMessage | SlashMessage, protected messageOptions: MessageOptions) {}
-
- /**
- * Sends a message with buttons for the user to confirm or cancel the action.
- */
- protected async send(): Promise<boolean> {
- this.messageOptions.components = [
- new ActionRowBuilder<ButtonBuilder>().addComponents(
- new ButtonBuilder({ style: ButtonStyle.Success, customId: 'confirmationPrompt_confirm', label: 'Yes' }),
- new ButtonBuilder({ style: ButtonStyle.Danger, customId: 'confirmationPrompt_cancel', label: 'No' })
- )
- ];
-
- const msg = await this.message.channel!.send(this.messageOptions);
-
- return await new Promise<boolean>((resolve) => {
- let responded = false;
- const collector = msg.createMessageComponentCollector({
- filter: (interaction) => interaction.message?.id == msg.id,
- time: 300_000
- });
-
- collector.on('collect', async (interaction: MessageComponentInteraction) => {
- await interaction.deferUpdate().catch(() => undefined);
- if (interaction.user.id == this.message.author.id || this.message.client.config.owners.includes(interaction.user.id)) {
- if (interaction.customId === 'confirmationPrompt_confirm') {
- responded = true;
- collector.stop();
- resolve(true);
- } else if (interaction.customId === 'confirmationPrompt_cancel') {
- responded = true;
- collector.stop();
- resolve(false);
- }
- }
- });
-
- collector.on('end', async () => {
- await msg.delete().catch(() => undefined);
- if (!responded) resolve(false);
- });
- });
- }
-
- /**
- * Sends a message with buttons for the user to confirm or cancel the action.
- * @param message The message that triggered the command
- * @param sendOptions Options for sending the message
- */
- public static async send(message: CommandMessage | SlashMessage, sendOptions: MessageOptions): Promise<boolean> {
- return new ConfirmationPrompt(message, sendOptions).send();
- }
-}
diff --git a/src/lib/common/DeleteButton.ts b/src/lib/common/DeleteButton.ts
deleted file mode 100644
index 340d07f..0000000
--- a/src/lib/common/DeleteButton.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { PaginateEmojis, type CommandMessage, type SlashMessage } from '#lib';
-import { CommandUtil } from 'discord-akairo';
-import {
- ActionRowBuilder,
- ButtonBuilder,
- ButtonStyle,
- MessageComponentInteraction,
- MessageEditOptions,
- MessagePayload,
- type MessageOptions
-} from 'discord.js';
-
-/**
- * Sends a message with a button for the user to delete it.
- */
-export class DeleteButton {
- /**
- * @param message The message to respond to
- * @param messageOptions The send message options
- */
- protected constructor(protected message: CommandMessage | SlashMessage, protected messageOptions: MessageOptions) {}
-
- /**
- * Sends a message with a button for the user to delete it.
- */
- protected async send() {
- this.updateComponents();
-
- const msg = await this.message.util.reply(this.messageOptions);
-
- const collector = msg.createMessageComponentCollector({
- filter: (interaction) => interaction.customId == 'paginate__stop' && interaction.message?.id == msg.id,
- time: 300000
- });
-
- collector.on('collect', async (interaction: MessageComponentInteraction) => {
- await interaction.deferUpdate().catch(() => undefined);
- if (interaction.user.id == this.message.author.id || this.message.client.config.owners.includes(interaction.user.id)) {
- if (msg.deletable && !CommandUtil.deletedMessages.has(msg.id)) await msg.delete();
- }
- });
-
- collector.on('end', async () => {
- this.updateComponents(true, true);
- await msg.edit(<string | MessagePayload | MessageEditOptions>this.messageOptions).catch(() => undefined);
- });
- }
-
- /**
- * Generates the components for the message
- * @param edit Whether or not the message is being edited
- * @param disable Whether or not to disable the buttons
- */
- protected updateComponents(edit = false, disable = false): void {
- this.messageOptions.components = [
- new ActionRowBuilder<ButtonBuilder>().addComponents(
- new ButtonBuilder({
- style: ButtonStyle.Primary,
- customId: 'paginate__stop',
- emoji: PaginateEmojis.STOP,
- disabled: disable
- })
- )
- ];
- if (edit) {
- this.messageOptions.reply = undefined;
- }
- }
-
- /**
- * Sends a message with a button for the user to delete it.
- * @param message The message to respond to
- * @param options The send message options
- */
- public static async send(message: CommandMessage | SlashMessage, options: Omit<MessageOptions, 'components'>) {
- return new DeleteButton(message, options).send();
- }
-}
diff --git a/src/lib/common/HighlightManager.ts b/src/lib/common/HighlightManager.ts
deleted file mode 100644
index 4f891b7..0000000
--- a/src/lib/common/HighlightManager.ts
+++ /dev/null
@@ -1,485 +0,0 @@
-import { addToArray, format, Highlight, removeFromArray, timestamp, type HighlightWord } from '#lib';
-import assert from 'assert/strict';
-import {
- ChannelType,
- Collection,
- GuildMember,
- type Channel,
- type Client,
- type Message,
- type Snowflake,
- type TextBasedChannel
-} from 'discord.js';
-import { colors, Time } from '../utils/BushConstants.js';
-import { sanitizeInputForDiscord } from './util/Format.js';
-
-const NOTIFY_COOLDOWN = 5 * Time.Minute;
-const OWNER_NOTIFY_COOLDOWN = 5 * Time.Minute;
-const LAST_MESSAGE_COOLDOWN = 5 * Time.Minute;
-
-type users = Set<Snowflake>;
-type channels = Set<Snowflake>;
-type word = HighlightWord;
-type guild = Snowflake;
-type user = Snowflake;
-type lastMessage = Date;
-type lastDM = Message;
-
-type lastDmInfo = [lastDM: lastDM, guild: guild, channel: Snowflake, highlights: HighlightWord[]];
-
-export class HighlightManager {
- /**
- * Cached guild highlights.
- */
- public readonly guildHighlights = new Collection<guild, Collection<word, users>>();
-
- //~ /**
- //~ * Cached global highlights.
- //~ */
- //~ public readonly globalHighlights = new Collection<word, users>();
-
- /**
- * A collection of cooldowns of when a user last sent a message in a particular guild.
- */
- public readonly userLastTalkedCooldown = new Collection<guild, Collection<user, lastMessage>>();
-
- /**
- * Users that users have blocked
- */
- public readonly userBlocks = new Collection<guild, Collection<user, users>>();
-
- /**
- * Channels that users have blocked
- */
- public readonly channelBlocks = new Collection<guild, Collection<user, channels>>();
-
- /**
- * A collection of cooldowns of when the bot last sent each user a highlight message.
- */
- public readonly lastedDMedUserCooldown = new Collection<user, lastDmInfo>();
-
- /**
- * @param client The client to use.
- */
- public constructor(public readonly client: Client) {}
-
- /**
- * Sync the cache with the database.
- */
- public async syncCache(): Promise<void> {
- const highlights = await Highlight.findAll();
-
- this.guildHighlights.clear();
-
- for (const highlight of highlights) {
- highlight.words.forEach((word) => {
- if (!this.guildHighlights.has(highlight.guild)) this.guildHighlights.set(highlight.guild, new Collection());
- const guildCache = this.guildHighlights.get(highlight.guild)!;
- if (!guildCache.get(word)) guildCache.set(word, new Set());
- guildCache.get(word)!.add(highlight.user);
- });
-
- if (!this.userBlocks.has(highlight.guild)) this.userBlocks.set(highlight.guild, new Collection());
- this.userBlocks.get(highlight.guild)!.set(highlight.user, new Set(highlight.blacklistedUsers));
-
- if (!this.channelBlocks.has(highlight.guild)) this.channelBlocks.set(highlight.guild, new Collection());
- this.channelBlocks.get(highlight.guild)!.set(highlight.user, new Set(highlight.blacklistedChannels));
- }
- }
-
- /**
- * Checks a message for highlights.
- * @param message The message to check.
- * @returns A collection users mapped to the highlight matched
- */
- public checkMessage(message: Message): Collection<Snowflake, HighlightWord> {
- // even if there are multiple matches, only the first one is returned
- const ret = new Collection<Snowflake, HighlightWord>();
- if (!message.content || !message.inGuild()) return ret;
- if (!this.guildHighlights.has(message.guildId)) return ret;
-
- const guildCache = this.guildHighlights.get(message.guildId)!;
-
- for (const [word, users] of guildCache.entries()) {
- if (!this.isMatch(message.content, word)) continue;
-
- for (const user of users) {
- if (ret.has(user)) continue;
-
- if (!message.channel.permissionsFor(user)?.has('ViewChannel')) continue;
-
- const blockedUsers = this.userBlocks.get(message.guildId)?.get(user) ?? new Set();
- if (blockedUsers.has(message.author.id)) {
- void this.client.console.verbose(
- 'Highlight',
- `Highlight ignored because <<${this.client.users.cache.get(user)?.tag ?? user}>> blocked the user <<${
- message.author.tag
- }>>`
- );
- continue;
- }
- const blockedChannels = this.channelBlocks.get(message.guildId)?.get(user) ?? new Set();
- if (blockedChannels.has(message.channel.id)) {
- void this.client.console.verbose(
- 'Highlight',
- `Highlight ignored because <<${this.client.users.cache.get(user)?.tag ?? user}>> blocked the channel <<${
- message.channel.name
- }>>`
- );
- continue;
- }
- if (message.mentions.has(user)) {
- void this.client.console.verbose(
- 'Highlight',
- `Highlight ignored because <<${this.client.users.cache.get(user)?.tag ?? user}>> is already mentioned in the message.`
- );
- continue;
- }
- ret.set(user, word);
- }
- }
-
- return ret;
- }
-
- /**
- * Checks a user provided phrase for their highlights.
- * @param guild The guild to check in.
- * @param user The user to get the highlights for.
- * @param phrase The phrase for highlights in.
- * @returns A collection of the user's highlights mapped to weather or not it was matched.
- */
- public async checkPhrase(guild: Snowflake, user: Snowflake, phrase: string): Promise<Collection<HighlightWord, boolean>> {
- const highlights = await Highlight.findAll({ where: { guild, user } });
-
- const results = new Collection<HighlightWord, boolean>();
-
- for (const highlight of highlights) {
- for (const word of highlight.words) {
- results.set(word, this.isMatch(phrase, word));
- }
- }
-
- return results;
- }
-
- /**
- * Checks a particular highlight for a match within a phrase.
- * @param phrase The phrase to check for the word in.
- * @param hl The highlight to check for.
- * @returns Whether or not the highlight was matched.
- */
- private isMatch(phrase: string, hl: HighlightWord): boolean {
- if (hl.regex) {
- return new RegExp(hl.word, 'gi').test(phrase);
- } else {
- if (hl.word.includes(' ')) {
- return phrase.toLocaleLowerCase().includes(hl.word.toLocaleLowerCase());
- } else {
- const words = phrase.split(/\s*\b\s/);
- return words.some((w) => w.toLocaleLowerCase() === hl.word.toLocaleLowerCase());
- }
- }
- }
-
- /**
- * Adds a new highlight to a user in a particular guild.
- * @param guild The guild to add the highlight to.
- * @param user The user to add the highlight to.
- * @param hl The highlight to add.
- * @returns A string representing a user error or a boolean indicating the database success.
- */
- public async addHighlight(guild: Snowflake, user: Snowflake, hl: HighlightWord): Promise<string | boolean> {
- if (!this.guildHighlights.has(guild)) this.guildHighlights.set(guild, new Collection());
- const guildCache = this.guildHighlights.get(guild)!;
-
- if (!guildCache.has(hl)) guildCache.set(hl, new Set());
- guildCache.get(hl)!.add(user);
-
- const [highlight] = await Highlight.findOrCreate({ where: { guild, user } });
-
- if (highlight.words.some((w) => w.word === hl.word)) return `You have already highlighted "${hl.word}".`;
-
- highlight.words = addToArray(highlight.words, hl);
-
- return Boolean(await highlight.save().catch(() => false));
- }
-
- /**
- * Removes a highlighted word for a user in a particular guild.
- * @param guild The guild to remove the highlight from.
- * @param user The user to remove the highlight from.
- * @param hl The word to remove.
- * @returns A string representing a user error or a boolean indicating the database success.
- */
- public async removeHighlight(guild: Snowflake, user: Snowflake, hl: string): Promise<string | boolean> {
- if (!this.guildHighlights.has(guild)) this.guildHighlights.set(guild, new Collection());
- const guildCache = this.guildHighlights.get(guild)!;
-
- const wordCache = guildCache.find((_, key) => key.word === hl);
-
- if (!wordCache?.has(user)) return `You have not highlighted "${hl}".`;
-
- wordCache!.delete(user);
-
- const [highlight] = await Highlight.findOrCreate({ where: { guild, user } });
-
- const toRemove = highlight.words.find((w) => w.word === hl);
- if (!toRemove) return `Uhhhhh... This shouldn't happen.`;
-
- highlight.words = removeFromArray(highlight.words, toRemove);
-
- return Boolean(await highlight.save().catch(() => false));
- }
-
- /**
- * Remove all highlight words for a user in a particular guild.
- * @param guild The guild to remove the highlights from.
- * @param user The user to remove the highlights from.
- * @returns A boolean indicating the database success.
- */
- public async removeAllHighlights(guild: Snowflake, user: Snowflake): Promise<boolean> {
- if (!this.guildHighlights.has(guild)) this.guildHighlights.set(guild, new Collection());
- const guildCache = this.guildHighlights.get(guild)!;
-
- for (const [word, users] of guildCache.entries()) {
- if (users.has(user)) users.delete(user);
- if (users.size === 0) guildCache.delete(word);
- }
-
- const highlight = await Highlight.findOne({ where: { guild, user } });
-
- if (!highlight) return false;
-
- highlight.words = [];
-
- return Boolean(await highlight.save().catch(() => false));
- }
-
- /**
- * Adds a new user or channel block to a user in a particular guild.
- * @param guild The guild to add the block to.
- * @param user The user that is blocking the target.
- * @param target The target that is being blocked.
- * @returns The result of the operation.
- */
- public async addBlock(
- guild: Snowflake,
- user: Snowflake,
- target: GuildMember | TextBasedChannel
- ): Promise<HighlightBlockResult> {
- const cacheKey = `${target instanceof GuildMember ? 'user' : 'channel'}Blocks` as const;
- const databaseKey = `blacklisted${target instanceof GuildMember ? 'Users' : 'Channels'}` as const;
-
- const [highlight] = await Highlight.findOrCreate({ where: { guild, user } });
-
- if (highlight[databaseKey].includes(target.id)) return HighlightBlockResult.ALREADY_BLOCKED;
-
- const newBlocks = addToArray(highlight[databaseKey], target.id);
-
- highlight[databaseKey] = newBlocks;
- const res = await highlight.save().catch(() => false);
- if (!res) return HighlightBlockResult.ERROR;
-
- if (!this[cacheKey].has(guild)) this[cacheKey].set(guild, new Collection());
- const guildBlocks = this[cacheKey].get(guild)!;
- guildBlocks.set(user, new Set(newBlocks));
-
- return HighlightBlockResult.SUCCESS;
- }
-
- /**
- * Removes a user or channel block from a user in a particular guild.
- * @param guild The guild to remove the block from.
- * @param user The user that is unblocking the target.
- * @param target The target that is being unblocked.
- * @returns The result of the operation.
- */
- public async removeBlock(guild: Snowflake, user: Snowflake, target: GuildMember | Channel): Promise<HighlightUnblockResult> {
- const cacheKey = `${target instanceof GuildMember ? 'user' : 'channel'}Blocks` as const;
- const databaseKey = `blacklisted${target instanceof GuildMember ? 'Users' : 'Channels'}` as const;
-
- const [highlight] = await Highlight.findOrCreate({ where: { guild, user } });
-
- if (!highlight[databaseKey].includes(target.id)) return HighlightUnblockResult.NOT_BLOCKED;
-
- const newBlocks = removeFromArray(highlight[databaseKey], target.id);
-
- highlight[databaseKey] = newBlocks;
- const res = await highlight.save().catch(() => false);
- if (!res) return HighlightUnblockResult.ERROR;
-
- if (!this[cacheKey].has(guild)) this[cacheKey].set(guild, new Collection());
- const guildBlocks = this[cacheKey].get(guild)!;
- guildBlocks.set(user, new Set(newBlocks));
-
- return HighlightUnblockResult.SUCCESS;
- }
-
- /**
- * Sends a user a direct message to alert them of their highlight being triggered.
- * @param message The message that triggered the highlight.
- * @param user The user who's highlights was triggered.
- * @param hl The highlight that was matched.
- * @returns Whether or a dm was sent.
- */
- public async notify(message: Message, user: Snowflake, hl: HighlightWord): Promise<boolean> {
- assert(message.inGuild());
-
- this.client.console.debug(`Notifying ${user} of highlight ${hl.word} in ${message.guild.name}`);
-
- dmCooldown: {
- const lastDM = this.lastedDMedUserCooldown.get(user);
- if (!lastDM?.[0]) break dmCooldown;
-
- const cooldown = this.client.config.owners.includes(user) ? OWNER_NOTIFY_COOLDOWN : NOTIFY_COOLDOWN;
-
- if (new Date().getTime() - lastDM[0].createdAt.getTime() < cooldown) {
- void this.client.console.verbose('Highlight', `User <<${user}>> has been DMed recently.`);
-
- if (lastDM[0].embeds.length < 10) {
- this.client.console.debug(`Trying to add to notification queue for ${user}`);
- return this.addToNotification(lastDM, message, hl);
- }
-
- this.client.console.debug(`User has too many embeds (${lastDM[0].embeds.length}).`);
- return false;
- }
- }
-
- talkCooldown: {
- const lastTalked = this.userLastTalkedCooldown.get(message.guildId)?.get(user);
- if (!lastTalked) break talkCooldown;
-
- presence: {
- // incase the bot left the guild
- if (message.guild) {
- const member = message.guild.members.cache.get(user);
- if (!member) {
- this.client.console.debug(`No member found for ${user} in ${message.guild.name}`);
- break presence;
- }
-
- const presence = member.presence ?? (await member.fetch()).presence;
- if (!presence) {
- this.client.console.debug(`No presence found for ${user} in ${message.guild.name}`);
- break presence;
- }
-
- if (presence.status === 'offline') {
- void this.client.console.verbose('Highlight', `User <<${user}>> is offline.`);
- break talkCooldown;
- }
- }
- }
-
- const now = new Date().getTime();
- const talked = lastTalked.getTime();
-
- if (now - talked < LAST_MESSAGE_COOLDOWN) {
- void this.client.console.verbose('Highlight', `User <<${user}>> has talked too recently.`);
-
- setTimeout(() => {
- const newTalked = this.userLastTalkedCooldown.get(message.guildId)?.get(user)?.getTime();
- if (talked !== newTalked) return;
-
- void this.notify(message, user, hl);
- }, LAST_MESSAGE_COOLDOWN).unref();
-
- return false;
- }
- }
-
- return this.client.users
- .send(user, {
- // eslint-disable-next-line @typescript-eslint/no-base-to-string
- content: `In ${format.input(message.guild.name)} ${message.channel}, your highlight "${hl.word}" was matched:`,
- embeds: [this.generateDmEmbed(message, hl)]
- })
- .then((dm) => {
- this.lastedDMedUserCooldown.set(user, [dm, message.guildId!, message.channelId, [hl]]);
- return true;
- })
- .catch(() => false);
- }
-
- private async addToNotification(
- [originalDm, guild, channel, originalHl]: lastDmInfo,
- message: Message,
- hl: HighlightWord
- ): Promise<boolean> {
- assert(originalDm.embeds.length < 10);
- assert(originalDm.embeds.length > 0);
- assert(originalDm.channel.type === ChannelType.DM);
- this.client.console.debug(
- `Adding to notification queue for ${originalDm.channel.recipient?.tag ?? originalDm.channel.recipientId}`
- );
-
- const sameGuild = guild === message.guildId;
- const sameChannel = channel === message.channel.id;
- const sameWord = originalHl.every((w) => w.word === hl.word);
-
- /* eslint-disable @typescript-eslint/no-base-to-string */
- return originalDm
- .edit({
- content: `In ${sameGuild ? format.input(message.guild?.name ?? '[Unknown]') : 'multiple servers'} ${
- sameChannel ? message.channel ?? '[Unknown]' : 'multiple channels'
- }, ${sameWord ? `your highlight "${hl.word}" was matched:` : 'multiple highlights were matched:'}`,
- embeds: [...originalDm.embeds.map((e) => e.toJSON()), this.generateDmEmbed(message, hl)]
- })
- .then(() => true)
- .catch(() => false);
- /* eslint-enable @typescript-eslint/no-base-to-string */
- }
-
- private generateDmEmbed(message: Message, hl: HighlightWord) {
- const recentMessages = message.channel.messages.cache
- .filter((m) => m.createdTimestamp <= message.createdTimestamp && m.id !== message.id)
- .filter((m) => m.cleanContent?.trim().length > 0)
- .sort((a, b) => b.createdTimestamp - a.createdTimestamp)
- .first(4)
- .reverse();
-
- return {
- description: [
- // eslint-disable-next-line @typescript-eslint/no-base-to-string
- message.channel!.toString(),
- ...[...recentMessages, message].map(
- (m) => `${timestamp(m.createdAt, 't')} ${format.input(`${m.author.tag}:`)} ${m.cleanContent.trim().substring(0, 512)}`
- )
- ].join('\n'),
- author: { name: hl.regex ? `/${hl.word}/gi` : hl.word },
- fields: [{ name: 'Source message', value: `[Jump to message](${message.url})` }],
- color: colors.default,
- footer: { text: `Triggered in ${sanitizeInputForDiscord(`${message.guild}`)}` },
- timestamp: message.createdAt.toISOString()
- };
- }
-
- /**
- * Updates the time that a user last talked in a particular guild.
- * @param message The message the user sent.
- */
- public updateLastTalked(message: Message): void {
- if (!message.inGuild()) return;
- const lastTalked = (
- this.userLastTalkedCooldown.has(message.guildId)
- ? this.userLastTalkedCooldown
- : this.userLastTalkedCooldown.set(message.guildId, new Collection())
- ).get(message.guildId)!;
-
- lastTalked.set(message.author.id, new Date());
- }
-}
-
-export enum HighlightBlockResult {
- ALREADY_BLOCKED,
- ERROR,
- SUCCESS
-}
-
-export enum HighlightUnblockResult {
- NOT_BLOCKED,
- ERROR,
- SUCCESS
-}
diff --git a/src/lib/common/Sentry.ts b/src/lib/common/Sentry.ts
deleted file mode 100644
index 2792203..0000000
--- a/src/lib/common/Sentry.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-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) {
- if (config.credentials.sentryDsn === null) throw TypeError('sentryDsn cannot be null');
-
- SentryNode.init({
- dsn: config.credentials.sentryDsn,
- environment: config.environment,
- tracesSampleRate: 1.0,
- integrations: [
- new RewriteFrames({
- root: rootdir
- }),
- new Integrations.OnUnhandledRejection({
- mode: 'none'
- })
- ]
- });
- }
-}
diff --git a/src/lib/common/tags.ts b/src/lib/common/tags.ts
deleted file mode 100644
index 098cf29..0000000
--- a/src/lib/common/tags.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/* 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 */
-
-/**
- * Strips the **initial** indentation from the beginning of each line in a multiline string.
- */
-export function stripIndent(strings: TemplateStringsArray, ...expressions: any[]) {
- const str = format(strings, ...expressions);
- // remove the shortest leading indentation from each line
- const match = str.match(/^[^\S\n]*(?=\S)/gm);
- const indent = match && Math.min(...match.map((el) => el.length));
- if (indent) {
- const regexp = new RegExp(`^.{${indent}}`, 'gm');
- return str.replace(regexp, '');
- }
- return str;
-}
-
-/**
- * Strips **all** of the indentation from the beginning of each line in a multiline string.
- */
-export function stripIndents(strings: TemplateStringsArray, ...expressions: any[]) {
- const str = format(strings, ...expressions);
- // remove all indentation from each line
- return str.replace(/^[^\S\n]+/gm, '');
-}
-
-function format(strings: TemplateStringsArray, ...expressions: any[]) {
- const str = strings
- .reduce((result, string, index) => ''.concat(result, expressions[index - 1], string))
- .replace(/[^\S\n]+$/gm, '')
- .replace(/^\n/, '');
- return str;
-}
diff --git a/src/lib/common/typings/BushInspectOptions.ts b/src/lib/common/typings/BushInspectOptions.ts
deleted file mode 100644
index 30ed01a..0000000
--- a/src/lib/common/typings/BushInspectOptions.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-import { type InspectOptions } from 'util';
-
-/**
- * {@link https://nodejs.org/api/util.html#utilinspectobject-showhidden-depth-colors util.inspect Options Documentation}
- */
-export interface BushInspectOptions extends InspectOptions {
- /**
- * If `true`, object's non-enumerable symbols and properties are included in the
- * formatted result. [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)
- * and [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) entries
- * are also included as well as user defined prototype properties (excluding method properties).
- *
- * @default false
- */
- showHidden?: boolean | undefined;
-
- /**
- * Specifies the number of times to recurse while formatting `object`. This is useful
- * for inspecting large objects. To recurse up to the maximum call stack size pass
- * `Infinity` or `null`.
- *
- * @default 2
- */
- depth?: number | null | undefined;
-
- /**
- * If `true`, the output is styled with ANSI color codes. Colors are customizable. See
- * [Customizing util.inspect colors](https://nodejs.org/api/util.html#util_customizing_util_inspect_colors).
- *
- * @default false
- */
- colors?: boolean | undefined;
-
- /**
- * If `false`, `[util.inspect.custom](depth, opts)` functions are not invoked.
- *
- * @default true
- */
- customInspect?: boolean | undefined;
-
- /**
- * If `true`, `Proxy` inspection includes the
- * [`target` and `handler`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#Terminology)
- * objects.
- *
- * @default false
- */
- showProxy?: boolean | undefined;
-
- /**
- * Specifies the maximum number of `Array`, [`TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray),
- * [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) and
- * [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) elements to
- * include when formatting. Set to `null` or `Infinity` to show all elements.
- * Set to `0` or negative to show no elements.
- *
- * @default 100
- */
- maxArrayLength?: number | null | undefined;
-
- /**
- * Specifies the maximum number of characters to include when formatting. Set to
- * `null` or `Infinity` to show all elements. Set to `0` or negative to show no
- * characters.
- *
- * @default 10000
- */
- maxStringLength?: number | null | undefined;
-
- /**
- * The length at which input values are split across multiple lines. Set to
- * `Infinity` to format the input as a single line (in combination with compact set
- * to `true` or any number >= `1`).
- *
- * @default 80
- */
- breakLength?: number | undefined;
-
- /**
- * Setting this to `false` causes each object key to be displayed on a new line. It
- * will break on new lines in text that is longer than `breakLength`. If set to a
- * number, the most `n` inner elements are united on a single line as long as all
- * properties fit into `breakLength`. Short array elements are also grouped together.
- *
- * @default 3
- */
- compact?: boolean | number | undefined;
-
- /**
- * If set to `true` or a function, all properties of an object, and `Set` and `Map`
- * entries are sorted in the resulting string. If set to `true` the
- * [default sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) is used.
- * If set to a function, it is used as a
- * [compare function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#parameters).
- *
- * @default false
- */
- sorted?: boolean | ((a: string, b: string) => number) | undefined;
-
- /**
- * If set to `true`, getters are inspected. If set to `'get'`, only getters without a
- * corresponding setter are inspected. If set to `'set'`, only getters with a
- * corresponding setter are inspected. This might cause side effects depending on
- * the getter function.
- *
- * @default false
- */
- getters?: 'get' | 'set' | boolean | undefined;
-
- /**
- * If set to `true`, an underscore is used to separate every three digits in all bigints and numbers.
- *
- * @default false
- */
- numericSeparator?: boolean;
-
- /**
- * Whether or not to inspect strings.
- *
- * @default false
- */
- inspectStrings?: boolean;
-}
diff --git a/src/lib/common/typings/CodeBlockLang.ts b/src/lib/common/typings/CodeBlockLang.ts
deleted file mode 100644
index d0eb4f3..0000000
--- a/src/lib/common/typings/CodeBlockLang.ts
+++ /dev/null
@@ -1,311 +0,0 @@
-export type CodeBlockLang =
- | '1c'
- | 'abnf'
- | 'accesslog'
- | 'actionscript'
- | 'ada'
- | 'arduino'
- | 'ino'
- | 'armasm'
- | 'arm'
- | 'avrasm'
- | 'actionscript'
- | 'as'
- | 'angelscript'
- | 'asc'
- | 'apache'
- | 'apacheconf'
- | 'applescript'
- | 'osascript'
- | 'arcade'
- | 'asciidoc'
- | 'adoc'
- | 'aspectj'
- | 'autohotkey'
- | 'autoit'
- | 'awk'
- | 'mawk'
- | 'nawk'
- | 'gawk'
- | 'bash'
- | 'sh'
- | 'zsh'
- | 'basic'
- | 'bnf'
- | 'brainfuck'
- | 'bf'
- | 'csharp'
- | 'cs'
- | 'c'
- | 'h'
- | 'cpp'
- | 'hpp'
- | 'cc'
- | 'hh'
- | 'c++'
- | 'h++'
- | 'cxx'
- | 'hxx'
- | 'cal'
- | 'cos'
- | 'cls'
- | 'cmake'
- | 'cmake.in'
- | 'coq'
- | 'csp'
- | 'css'
- | 'capnproto'
- | 'capnp'
- | 'clojure'
- | 'clj'
- | 'coffeescript'
- | 'coffee'
- | 'cson'
- | 'iced'
- | 'crmsh'
- | 'crm'
- | 'pcmk'
- | 'crystal'
- | 'cr'
- | 'd'
- | 'dns'
- | 'zone'
- | 'bind'
- | 'dos'
- | 'bat'
- | 'cmd'
- | 'dart'
- | 'dpr'
- | 'dfm'
- | 'pas'
- | 'pascal'
- | 'diff'
- | 'patch'
- | 'django'
- | 'jinja'
- | 'dockerfile'
- | 'docker'
- | 'dsconfig'
- | 'dts'
- | 'dust'
- | 'dst'
- | 'ebnf'
- | 'elixir'
- | 'elm'
- | 'erlang'
- | 'erl'
- | 'excel'
- | 'xls'
- | 'xlsx'
- | 'fsharp'
- | 'fs'
- | 'fix'
- | 'fortran'
- | 'f90'
- | 'f95'
- | 'gcode'
- | 'nc'
- | 'gams'
- | 'gms'
- | 'gauss'
- | 'gss'
- | 'gherkin'
- | 'go'
- | 'golang'
- | 'golo'
- | 'gololang'
- | 'gradle'
- | 'groovy'
- | 'xml'
- | 'html'
- | 'xhtml'
- | 'rss'
- | 'atom'
- | 'xjb'
- | 'xsd'
- | 'xsl'
- | 'plist'
- | 'svg'
- | 'http'
- | 'https'
- | 'haml'
- | 'handlebars'
- | 'hbs'
- | 'html.hbs'
- | 'html.handlebars'
- | 'haskell'
- | 'hs'
- | 'haxe'
- | 'hx'
- | 'hlsl'
- | 'hy'
- | 'hylang'
- | 'ini'
- | 'toml'
- | 'inform7'
- | 'i7'
- | 'irpf90'
- | 'json'
- | 'java'
- | 'jsp'
- | 'javascript'
- | 'js'
- | 'jsx'
- | 'julia'
- | 'julia-repl'
- | 'kotlin'
- | 'kt'
- | 'tex'
- | 'leaf'
- | 'lasso'
- | 'ls'
- | 'lassoscript'
- | 'less'
- | 'ldif'
- | 'lisp'
- | 'livecodeserver'
- | 'livescript'
- | 'ls'
- | 'lua'
- | 'makefile'
- | 'mk'
- | 'mak'
- | 'make'
- | 'markdown'
- | 'md'
- | 'mkdown'
- | 'mkd'
- | 'mathematica'
- | 'mma'
- | 'wl'
- | 'matlab'
- | 'maxima'
- | 'mel'
- | 'mercury'
- | 'mizar'
- | 'mojolicious'
- | 'monkey'
- | 'moonscript'
- | 'moon'
- | 'n1ql'
- | 'nsis'
- | 'nginx'
- | 'nginxconf'
- | 'nim'
- | 'nimrod'
- | 'nix'
- | 'ocaml'
- | 'ml'
- | 'objectivec'
- | 'mm'
- | 'objc'
- | 'obj-c'
- | 'obj-c++'
- | 'objective-c++'
- | 'glsl'
- | 'openscad'
- | 'scad'
- | 'ruleslanguage'
- | 'oxygene'
- | 'pf'
- | 'pf.conf'
- | 'php'
- | 'parser3'
- | 'perl'
- | 'pl'
- | 'pm'
- | 'plaintext'
- | 'txt'
- | 'text'
- | 'pony'
- | 'pgsql'
- | 'postgres'
- | 'postgresql'
- | 'powershell'
- | 'ps'
- | 'ps1'
- | 'processing'
- | 'prolog'
- | 'properties'
- | 'protobuf'
- | 'puppet'
- | 'pp'
- | 'python'
- | 'py'
- | 'gyp'
- | 'profile'
- | 'python-repl'
- | 'pycon'
- | 'k'
- | 'kdb'
- | 'qml'
- | 'r'
- | 'reasonml'
- | 're'
- | 'rib'
- | 'rsl'
- | 'graph'
- | 'instances'
- | 'ruby'
- | 'rb'
- | 'gemspec'
- | 'podspec'
- | 'thor'
- | 'irb'
- | 'rust'
- | 'rs'
- | 'sas'
- | 'scss'
- | 'sql'
- | 'p21'
- | 'step'
- | 'stp'
- | 'scala'
- | 'scheme'
- | 'scilab'
- | 'sci'
- | 'shell'
- | 'console'
- | 'smali'
- | 'smalltalk'
- | 'st'
- | 'sml'
- | 'ml'
- | 'stan'
- | 'stanfuncs'
- | 'stata'
- | 'stylus'
- | 'styl'
- | 'subunit'
- | 'swift'
- | 'tcl'
- | 'tk'
- | 'tap'
- | 'thrift'
- | 'tp'
- | 'twig'
- | 'craftcms'
- | 'typescript'
- | 'ts'
- | 'vbnet'
- | 'vb'
- | 'vbscript'
- | 'vbs'
- | 'vhdl'
- | 'vala'
- | 'verilog'
- | 'v'
- | 'vim'
- | 'axapta'
- | 'x++'
- | 'x86asm'
- | 'xl'
- | 'tao'
- | 'xquery'
- | 'xpath'
- | 'xq'
- | 'yml'
- | 'yaml'
- | 'zephir'
- | 'zep'
- | 'ansi';
diff --git a/src/lib/common/util/Arg.ts b/src/lib/common/util/Arg.ts
deleted file mode 100644
index d362225..0000000
--- a/src/lib/common/util/Arg.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-import {
- type BaseBushArgumentType,
- type BushArgumentType,
- type BushArgumentTypeCaster,
- type CommandMessage,
- type SlashMessage
-} from '#lib';
-import { Argument, type Command, type Flag, type ParsedValuePredicate } from 'discord-akairo';
-import { type Message } from 'discord.js';
-
-/**
- * Casts a phrase to this argument's type.
- * @param type - The type to cast to.
- * @param message - Message that called the command.
- * @param phrase - Phrase to process.
- */
-export async function cast<T extends ATC>(type: T, message: CommandMessage | SlashMessage, phrase: string): Promise<ATCR<T>>;
-export async function cast<T extends KBAT>(type: T, message: CommandMessage | SlashMessage, phrase: string): Promise<BAT[T]>;
-export async function cast(type: AT | ATC, message: CommandMessage | SlashMessage, phrase: string): Promise<any>;
-export async function cast(
- this: ThisType<Command>,
- type: ATC | AT,
- message: CommandMessage | SlashMessage,
- phrase: string
-): Promise<any> {
- return Argument.cast.call(this, type as any, message.client.commandHandler.resolver, message as Message, phrase);
-}
-
-/**
- * Creates a type that is the left-to-right composition of the given types.
- * If any of the types fails, the entire composition fails.
- * @param types - Types to use.
- */
-export function compose<T extends ATC>(...types: T[]): ATCATCR<T>;
-export function compose<T extends KBAT>(...types: T[]): ATCBAT<T>;
-export function compose(...types: (AT | ATC)[]): ATC;
-export function compose(...types: (AT | ATC)[]): ATC {
- return Argument.compose(...(types as any));
-}
-
-/**
- * Creates a type that is the left-to-right composition of the given types.
- * If any of the types fails, the composition still continues with the failure passed on.
- * @param types - Types to use.
- */
-export function composeWithFailure<T extends ATC>(...types: T[]): ATCATCR<T>;
-export function composeWithFailure<T extends KBAT>(...types: T[]): ATCBAT<T>;
-export function composeWithFailure(...types: (AT | ATC)[]): ATC;
-export function composeWithFailure(...types: (AT | ATC)[]): ATC {
- return Argument.composeWithFailure(...(types as any));
-}
-
-/**
- * Checks if something is null, undefined, or a fail flag.
- * @param value - Value to check.
- */
-export function isFailure(value: any): value is null | undefined | (Flag & { value: any }) {
- return Argument.isFailure(value);
-}
-
-/**
- * Creates a type from multiple types (product type).
- * Only inputs where each type resolves with a non-void value are valid.
- * @param types - Types to use.
- */
-export function product<T extends ATC>(...types: T[]): ATCATCR<T>;
-export function product<T extends KBAT>(...types: T[]): ATCBAT<T>;
-export function product(...types: (AT | ATC)[]): ATC;
-export function product(...types: (AT | ATC)[]): ATC {
- return Argument.product(...(types as any));
-}
-
-/**
- * Creates a type where the parsed value must be within a range.
- * @param type - The type to use.
- * @param min - Minimum value.
- * @param max - Maximum value.
- * @param inclusive - Whether or not to be inclusive on the upper bound.
- */
-export function range<T extends ATC>(type: T, min: number, max: number, inclusive?: boolean): ATCATCR<T>;
-export function range<T extends KBAT>(type: T, min: number, max: number, inclusive?: boolean): ATCBAT<T>;
-export function range(type: AT | ATC, min: number, max: number, inclusive?: boolean): ATC;
-export function range(type: AT | ATC, min: number, max: number, inclusive?: boolean): ATC {
- return Argument.range(type as any, min, max, inclusive);
-}
-
-/**
- * Creates a type that parses as normal but also tags it with some data.
- * Result is in an object `{ tag, value }` and wrapped in `Flag.fail` when failed.
- * @param type - The type to use.
- * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string.
- */
-export function tagged<T extends ATC>(type: T, tag?: any): ATCATCR<T>;
-export function tagged<T extends KBAT>(type: T, tag?: any): ATCBAT<T>;
-export function tagged(type: AT | ATC, tag?: any): ATC;
-export function tagged(type: AT | ATC, tag?: any): ATC {
- return Argument.tagged(type as any, tag);
-}
-
-/**
- * Creates a type from multiple types (union type).
- * The first type that resolves to a non-void value is used.
- * Each type will also be tagged using `tagged` with themselves.
- * @param types - Types to use.
- */
-export function taggedUnion<T extends ATC>(...types: T[]): ATCATCR<T>;
-export function taggedUnion<T extends KBAT>(...types: T[]): ATCBAT<T>;
-export function taggedUnion(...types: (AT | ATC)[]): ATC;
-export function taggedUnion(...types: (AT | ATC)[]): ATC {
- return Argument.taggedUnion(...(types as any));
-}
-
-/**
- * Creates a type that parses as normal but also tags it with some data and carries the original input.
- * Result is in an object `{ tag, input, value }` and wrapped in `Flag.fail` when failed.
- * @param type - The type to use.
- * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string.
- */
-export function taggedWithInput<T extends ATC>(type: T, tag?: any): ATCATCR<T>;
-export function taggedWithInput<T extends KBAT>(type: T, tag?: any): ATCBAT<T>;
-export function taggedWithInput(type: AT | ATC, tag?: any): ATC;
-export function taggedWithInput(type: AT | ATC, tag?: any): ATC {
- return Argument.taggedWithInput(type as any, tag);
-}
-
-/**
- * Creates a type from multiple types (union type).
- * The first type that resolves to a non-void value is used.
- * @param types - Types to use.
- */
-export function union<T extends ATC>(...types: T[]): ATCATCR<T>;
-export function union<T extends KBAT>(...types: T[]): ATCBAT<T>;
-export function union(...types: (AT | ATC)[]): ATC;
-export function union(...types: (AT | ATC)[]): ATC {
- return Argument.union(...(types as any));
-}
-
-/**
- * Creates a type with extra validation.
- * If the predicate is not true, the value is considered invalid.
- * @param type - The type to use.
- * @param predicate - The predicate function.
- */
-export function validate<T extends ATC>(type: T, predicate: ParsedValuePredicate): ATCATCR<T>;
-export function validate<T extends KBAT>(type: T, predicate: ParsedValuePredicate): ATCBAT<T>;
-export function validate(type: AT | ATC, predicate: ParsedValuePredicate): ATC;
-export function validate(type: AT | ATC, predicate: ParsedValuePredicate): ATC {
- return Argument.validate(type as any, predicate);
-}
-
-/**
- * Creates a type that parses as normal but also carries the original input.
- * Result is in an object `{ input, value }` and wrapped in `Flag.fail` when failed.
- * @param type - The type to use.
- */
-export function withInput<T extends ATC>(type: T): ATC<ATCR<T>>;
-export function withInput<T extends KBAT>(type: T): ATCBAT<T>;
-export function withInput(type: AT | ATC): ATC;
-export function withInput(type: AT | ATC): ATC {
- return Argument.withInput(type as any);
-}
-
-type BushArgumentTypeCasterReturn<R> = R extends BushArgumentTypeCaster<infer S> ? S : R;
-/** ```ts
- * <R = unknown> = BushArgumentTypeCaster<R>
- * ``` */
-type ATC<R = unknown> = BushArgumentTypeCaster<R>;
-/** ```ts
- * keyof BaseBushArgumentType
- * ``` */
-type KBAT = keyof BaseBushArgumentType;
-/** ```ts
- * <R> = BushArgumentTypeCasterReturn<R>
- * ``` */
-type ATCR<R> = BushArgumentTypeCasterReturn<R>;
-/** ```ts
- * BushArgumentType
- * ``` */
-type AT = BushArgumentType;
-/** ```ts
- * BaseBushArgumentType
- * ``` */
-type BAT = BaseBushArgumentType;
-
-/** ```ts
- * <T extends BushArgumentTypeCaster> = BushArgumentTypeCaster<BushArgumentTypeCasterReturn<T>>
- * ``` */
-type ATCATCR<T extends BushArgumentTypeCaster> = BushArgumentTypeCaster<BushArgumentTypeCasterReturn<T>>;
-/** ```ts
- * <T extends keyof BaseBushArgumentType> = BushArgumentTypeCaster<BaseBushArgumentType[T]>
- * ``` */
-type ATCBAT<T extends keyof BaseBushArgumentType> = BushArgumentTypeCaster<BaseBushArgumentType[T]>;
diff --git a/src/lib/common/util/Format.ts b/src/lib/common/util/Format.ts
deleted file mode 100644
index debaf4b..0000000
--- a/src/lib/common/util/Format.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import { type CodeBlockLang } from '#lib';
-import {
- bold as discordBold,
- codeBlock as discordCodeBlock,
- escapeBold as discordEscapeBold,
- escapeCodeBlock as discordEscapeCodeBlock,
- escapeInlineCode as discordEscapeInlineCode,
- escapeItalic as discordEscapeItalic,
- escapeMarkdown,
- escapeSpoiler as discordEscapeSpoiler,
- escapeStrikethrough as discordEscapeStrikethrough,
- escapeUnderline as discordEscapeUnderline,
- inlineCode as discordInlineCode,
- italic as discordItalic,
- spoiler as discordSpoiler,
- strikethrough as discordStrikethrough,
- underscore as discordUnderscore
-} from 'discord.js';
-
-/**
- * Wraps the content inside a codeblock with no language.
- * @param content The content to wrap.
- */
-export function codeBlock(content: string): string;
-
-/**
- * Wraps the content inside a codeblock with the specified language.
- * @param language The language for the codeblock.
- * @param content The content to wrap.
- */
-export function codeBlock(language: CodeBlockLang, content: string): string;
-export function codeBlock(languageOrContent: string, content?: string): string {
- return typeof content === 'undefined'
- ? discordCodeBlock(discordEscapeCodeBlock(`${languageOrContent}`))
- : discordCodeBlock(`${languageOrContent}`, discordEscapeCodeBlock(`${content}`));
-}
-
-/**
- * Wraps the content inside \`backticks\`, which formats it as inline code.
- * @param content The content to wrap.
- */
-export function inlineCode(content: string): string {
- return discordInlineCode(discordEscapeInlineCode(`${content}`));
-}
-
-/**
- * Formats the content into italic text.
- * @param content The content to wrap.
- */
-export function italic(content: string): string {
- return discordItalic(discordEscapeItalic(`${content}`));
-}
-
-/**
- * Formats the content into bold text.
- * @param content The content to wrap.
- */
-export function bold(content: string): string {
- return discordBold(discordEscapeBold(`${content}`));
-}
-
-/**
- * Formats the content into underscored text.
- * @param content The content to wrap.
- */
-export function underscore(content: string): string {
- return discordUnderscore(discordEscapeUnderline(`${content}`));
-}
-
-/**
- * Formats the content into strike-through text.
- * @param content The content to wrap.
- */
-export function strikethrough(content: string): string {
- return discordStrikethrough(discordEscapeStrikethrough(`${content}`));
-}
-
-/**
- * Wraps the content inside spoiler (hidden text).
- * @param content The content to wrap.
- */
-export function spoiler(content: string): string {
- return discordSpoiler(discordEscapeSpoiler(`${content}`));
-}
-
-/**
- * Formats input: makes it bold and escapes any other markdown
- * @param text The input
- */
-export function input(text: string): string {
- return bold(sanitizeInputForDiscord(`${text}`));
-}
-
-/**
- * Formats input for logs: makes it highlighted
- * @param text The input
- */
-export function inputLog(text: string): string {
- return `<<${sanitizeWtlAndControl(`${text}`)}>>`;
-}
-
-/**
- * Removes all characters in a string that are either control characters or change the direction of text etc.
- * @param str The string you would like sanitized
- */
-export function sanitizeWtlAndControl(str: string) {
- // eslint-disable-next-line no-control-regex
- return `${str}`.replace(/[\u0000-\u001F\u007F-\u009F\u200B]/g, '');
-}
-
-/**
- * Removed wtl and control characters and escapes any other markdown
- * @param text The input
- */
-export function sanitizeInputForDiscord(text: string): string {
- return escapeMarkdown(sanitizeWtlAndControl(`${text}`));
-}
-
-export { escapeMarkdown } from 'discord.js';
diff --git a/src/lib/common/util/Minecraft.ts b/src/lib/common/util/Minecraft.ts
deleted file mode 100644
index a12ebf2..0000000
--- a/src/lib/common/util/Minecraft.ts
+++ /dev/null
@@ -1,349 +0,0 @@
-import { Byte, Int, parse } from '@ironm00n/nbt-ts';
-import { BitField } from 'discord.js';
-import path from 'path';
-import { fileURLToPath } from 'url';
-
-const __dirname = path.dirname(fileURLToPath(import.meta.url));
-
-export enum FormattingCodes {
- Black = '§0',
- DarkBlue = '§1',
- DarkGreen = '§2',
- DarkAqua = '§3',
- DarkRed = '§4',
- DarkPurple = '§5',
- Gold = '§6',
- Gray = '§7',
- DarkGray = '§8',
- Blue = '§9',
- Green = '§a',
- Aqua = '§b',
- Red = '§c',
- LightPurple = '§d',
- Yellow = '§e',
- White = '§f',
-
- Obfuscated = '§k',
- Bold = '§l',
- Strikethrough = '§m',
- Underline = '§n',
- Italic = '§o',
- Reset = '§r'
-}
-
-// https://minecraft.fandom.com/wiki/Formatting_codes
-export const formattingInfo = {
- [FormattingCodes.Black]: {
- foreground: 'rgb(0, 0, 0)',
- foregroundDarker: 'rgb(0, 0, 0)',
- background: 'rgb(0, 0, 0)',
- backgroundDarker: 'rgb(0, 0, 0)',
- ansi: '\u001b[0;30m'
- },
- [FormattingCodes.DarkBlue]: {
- foreground: 'rgb(0, 0, 170)',
- foregroundDarker: 'rgb(0, 0, 118)',
- background: 'rgb(0, 0, 42)',
- backgroundDarker: 'rgb(0, 0, 29)',
- ansi: '\u001b[0;34m'
- },
- [FormattingCodes.DarkGreen]: {
- foreground: 'rgb(0, 170, 0)',
- foregroundDarker: 'rgb(0, 118, 0)',
- background: 'rgb(0, 42, 0)',
- backgroundDarker: 'rgb(0, 29, 0)',
- ansi: '\u001b[0;32m'
- },
- [FormattingCodes.DarkAqua]: {
- foreground: 'rgb(0, 170, 170)',
- foregroundDarker: 'rgb(0, 118, 118)',
- background: 'rgb(0, 42, 42)',
- backgroundDarker: 'rgb(0, 29, 29)',
- ansi: '\u001b[0;36m'
- },
- [FormattingCodes.DarkRed]: {
- foreground: 'rgb(170, 0, 0)',
- foregroundDarker: 'rgb(118, 0, 0)',
- background: 'rgb(42, 0, 0)',
- backgroundDarker: 'rgb(29, 0, 0)',
- ansi: '\u001b[0;31m'
- },
- [FormattingCodes.DarkPurple]: {
- foreground: 'rgb(170, 0, 170)',
- foregroundDarker: 'rgb(118, 0, 118)',
- background: 'rgb(42, 0, 42)',
- backgroundDarker: 'rgb(29, 0, 29)',
- ansi: '\u001b[0;35m'
- },
- [FormattingCodes.Gold]: {
- foreground: 'rgb(255, 170, 0)',
- foregroundDarker: 'rgb(178, 118, 0)',
- background: 'rgb(42, 42, 0)',
- backgroundDarker: 'rgb(29, 29, 0)',
- ansi: '\u001b[0;33m'
- },
- [FormattingCodes.Gray]: {
- foreground: 'rgb(170, 170, 170)',
- foregroundDarker: 'rgb(118, 118, 118)',
- background: 'rgb(42, 42, 42)',
- backgroundDarker: 'rgb(29, 29, 29)',
- ansi: '\u001b[0;37m'
- },
- [FormattingCodes.DarkGray]: {
- foreground: 'rgb(85, 85, 85)',
- foregroundDarker: 'rgb(59, 59, 59)',
- background: 'rgb(21, 21, 21)',
- backgroundDarker: 'rgb(14, 14, 14)',
- ansi: '\u001b[0;90m'
- },
- [FormattingCodes.Blue]: {
- foreground: 'rgb(85, 85, 255)',
- foregroundDarker: 'rgb(59, 59, 178)',
- background: 'rgb(21, 21, 63)',
- backgroundDarker: 'rgb(14, 14, 44)',
- ansi: '\u001b[0;94m'
- },
- [FormattingCodes.Green]: {
- foreground: 'rgb(85, 255, 85)',
- foregroundDarker: 'rgb(59, 178, 59)',
- background: 'rgb(21, 63, 21)',
- backgroundDarker: 'rgb(14, 44, 14)',
- ansi: '\u001b[0;92m'
- },
- [FormattingCodes.Aqua]: {
- foreground: 'rgb(85, 255, 255)',
- foregroundDarker: 'rgb(59, 178, 178)',
- background: 'rgb(21, 63, 63)',
- backgroundDarker: 'rgb(14, 44, 44)',
- ansi: '\u001b[0;96m'
- },
- [FormattingCodes.Red]: {
- foreground: 'rgb(255, 85, 85)',
- foregroundDarker: 'rgb(178, 59, 59)',
- background: 'rgb(63, 21, 21)',
- backgroundDarker: 'rgb(44, 14, 14)',
- ansi: '\u001b[0;91m'
- },
- [FormattingCodes.LightPurple]: {
- foreground: 'rgb(255, 85, 255)',
- foregroundDarker: 'rgb(178, 59, 178)',
- background: 'rgb(63, 21, 63)',
- backgroundDarker: 'rgb(44, 14, 44)',
- ansi: '\u001b[0;95m'
- },
- [FormattingCodes.Yellow]: {
- foreground: 'rgb(255, 255, 85)',
- foregroundDarker: 'rgb(178, 178, 59)',
- background: 'rgb(63, 63, 21)',
- backgroundDarker: 'rgb(44, 44, 14)',
- ansi: '\u001b[0;93m'
- },
- [FormattingCodes.White]: {
- foreground: 'rgb(255, 255, 255)',
- foregroundDarker: 'rgb(178, 178, 178)',
- background: 'rgb(63, 63, 63)',
- backgroundDarker: 'rgb(44, 44, 44)',
- ansi: '\u001b[0;97m'
- },
-
- [FormattingCodes.Obfuscated]: { ansi: '\u001b[8m' },
- [FormattingCodes.Bold]: { ansi: '\u001b[1m' },
- [FormattingCodes.Strikethrough]: { ansi: '\u001b[9m' },
- [FormattingCodes.Underline]: { ansi: '\u001b[4m' },
- [FormattingCodes.Italic]: { ansi: '\u001b[3m' },
- [FormattingCodes.Reset]: { ansi: '\u001b[0m' }
-} as const;
-
-export type McItemId = Lowercase<string>;
-export type SbItemId = Uppercase<string>;
-export type MojangJson = string;
-export type SbRecipeItem = `${SbItemId}:${number}` | '';
-export type SbRecipe = {
- [Location in `${'A' | 'B' | 'C'}${1 | 2 | 3}`]: SbRecipeItem;
-};
-export type InfoType = 'WIKI_URL' | '';
-
-export type Slayer = `${'WOLF' | 'BLAZE' | 'EMAN'}_${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`;
-
-export interface RawNeuItem {
- itemid: McItemId;
- displayname: string;
- nbttag: MojangJson;
- damage: number;
- lore: string[];
- recipe?: SbRecipe;
- internalname: SbItemId;
- modver: string;
- infoType: InfoType;
- info?: string[];
- crafttext: string;
- vanilla?: boolean;
- useneucraft?: boolean;
- slayer_req?: Slayer;
- clickcommand?: string;
- x?: number;
- y?: number;
- z?: number;
- island?: string;
- recipes?: { type: string; cost: any[]; result: SbItemId }[];
- /** @deprecated */
- parent?: SbItemId;
- noseal?: boolean;
-}
-
-export enum HideFlagsBits {
- Enchantments = 1,
- AttributeModifiers = 2,
- Unbreakable = 4,
- CanDestroy = 8,
- CanPlaceOn = 16,
- /**
- * potion effects, shield pattern info, "StoredEnchantments", written book
- * "generation" and "author", "Explosion", "Fireworks", and map tooltips
- */
- OtherInformation = 32,
- Dyed = 64
-}
-
-export type HideFlagsString = keyof typeof HideFlagsBits;
-
-export class HideFlags extends BitField<HideFlagsString> {
- public static override Flags = HideFlagsBits;
-}
-
-export const formattingCode = new RegExp(
- `§[${Object.values(FormattingCodes)
- .filter((v) => v.startsWith('§'))
- .map((v) => v.substring(1))
- .join('')}]`
-);
-
-export function removeMCFormatting(str: string) {
- return str.replaceAll(formattingCode, '');
-}
-
-const repo = path.join(__dirname, '..', '..', '..', '..', '..', 'neu-item-repo-dangerous');
-
-export interface NbtTag {
- overrideMeta?: Byte;
- Unbreakable?: Int;
- ench?: string[];
- HideFlags?: HideFlags;
- SkullOwner?: SkullOwner;
- display?: NbtTagDisplay;
- ExtraAttributes?: ExtraAttributes;
-}
-
-export interface SkullOwner {
- Id?: string;
- Properties?: {
- textures?: { Value?: string }[];
- };
-}
-
-export interface NbtTagDisplay {
- Lore?: string[];
- color?: Int;
- Name?: string;
-}
-
-export type RuneId = string;
-
-export interface ExtraAttributes {
- originTag?: Origin;
- id?: string;
- generator_tier?: Int;
- boss_tier?: Int;
- enchantments?: { hardened_mana?: Int };
- dungeon_item_level?: Int;
- runes?: { [key: RuneId]: Int };
- petInfo?: PetInfo;
-}
-
-export interface PetInfo {
- type: 'ZOMBIE';
- active: boolean;
- exp: number;
- tier: 'COMMON' | 'UNCOMMON' | 'RARE' | 'EPIC' | 'LEGENDARY';
- hideInfo: boolean;
- candyUsed: number;
-}
-
-export type Origin = 'SHOP_PURCHASE';
-
-const neuConstantsPath = path.join(repo, 'constants');
-const neuPetsPath = path.join(neuConstantsPath, 'pets.json');
-const neuPets = (await import(neuPetsPath, { assert: { type: 'json' } })) as PetsConstants;
-const neuPetNumsPath = path.join(neuConstantsPath, 'petnums.json');
-const neuPetNums = (await import(neuPetNumsPath, { assert: { type: 'json' } })) as PetNums;
-
-export interface PetsConstants {
- pet_rarity_offset: Record<string, number>;
- pet_levels: number[];
- custom_pet_leveling: Record<string, { type: number; pet_levels: number[]; max_level: number }>;
- pet_types: Record<string, string>;
-}
-
-export interface PetNums {
- [key: string]: {
- [key: string]: {
- '1': {
- otherNums: number[];
- statNums: Record<string, number>;
- };
- '100': {
- otherNums: number[];
- statNums: Record<string, number>;
- };
- 'stats_levelling_curve'?: `${number};${number};${number}`;
- };
- };
-}
-
-export class NeuItem {
- public itemId: McItemId;
- public displayName: string;
- public nbtTag: NbtTag;
- public internalName: SbItemId;
- public lore: string[];
-
- public constructor(raw: RawNeuItem) {
- this.itemId = raw.itemid;
- this.nbtTag = <NbtTag>parse(raw.nbttag);
- this.displayName = raw.displayname;
- this.internalName = raw.internalname;
- this.lore = raw.lore;
-
- this.petLoreReplacements();
- }
-
- private petLoreReplacements(level = -1) {
- if (/.*?;[0-5]$/.test(this.internalName) && this.displayName.includes('LVL')) {
- const maxLevel = neuPets?.custom_pet_leveling?.[this.internalName]?.max_level ?? 100;
- this.displayName = this.displayName.replace('LVL', `1➡${maxLevel}`);
-
- const nums = neuPetNums[this.internalName];
- if (!nums) throw new Error(`Pet (${this.internalName}) has no pet nums.`);
-
- const teir = ['COMMON', 'UNCOMMON', 'RARE', 'EPIC', 'LEGENDARY', 'MYTHIC'][+this.internalName.at(-1)!];
- const petInfoTier = nums[teir];
- if (!petInfoTier) throw new Error(`Pet (${this.internalName}) has no pet nums for ${teir} rarity.`);
-
- const curve = petInfoTier?.stats_levelling_curve?.split(';');
-
- // todo: finish copying from neu
-
- const minStatsLevel = parseInt(curve?.[0] ?? '0');
- const maxStatsLevel = parseInt(curve?.[0] ?? '100');
-
- const lore = '';
- }
- }
-}
-
-export function mcToAnsi(str: string) {
- for (const format in formattingInfo) {
- str = str.replaceAll(format, formattingInfo[format as keyof typeof formattingInfo].ansi);
- }
- return `${str}\u001b[0m`;
-}
diff --git a/src/lib/common/util/Minecraft_Test.ts b/src/lib/common/util/Minecraft_Test.ts
deleted file mode 100644
index 26ca648..0000000
--- a/src/lib/common/util/Minecraft_Test.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import fs from 'fs/promises';
-import path from 'path';
-import { fileURLToPath } from 'url';
-import { mcToAnsi, RawNeuItem } from './Minecraft.js';
-
-const __dirname = path.dirname(fileURLToPath(import.meta.url));
-const repo = path.join(__dirname, '..', '..', '..', '..', '..', 'neu-item-repo-dangerous');
-const itemPath = path.join(repo, 'items');
-const items = await fs.readdir(itemPath);
-
-// for (let i = 0; i < 5; i++) {
-for (const path_ of items) {
- // const randomItem = items[Math.floor(Math.random() * items.length)];
- // console.log(randomItem);
- const item = (await import(path.join(itemPath, /* randomItem */ path_), { assert: { type: 'json' } })).default as RawNeuItem;
- if (/.*?((_MONSTER)|(_NPC)|(_ANIMAL)|(_MINIBOSS)|(_BOSS)|(_SC))$/.test(item.internalname)) continue;
- if (!/.*?;[0-5]$/.test(item.internalname)) continue;
- /* console.log(path_);
- console.dir(item, { depth: Infinity }); */
-
- /* console.log('==========='); */
- // const nbt = parse(item.nbttag) as NbtTag;
-
- // if (nbt?.SkullOwner?.Properties?.textures?.[0]?.Value) {
- // nbt.SkullOwner.Properties.textures[0].Value = parse(
- // Buffer.from(nbt.SkullOwner.Properties.textures[0].Value, 'base64').toString('utf-8')
- // ) as string;
- // }
-
- // if (nbt.ExtraAttributes?.petInfo) {
- // nbt.ExtraAttributes.petInfo = JSON.parse(nbt.ExtraAttributes.petInfo as any as string);
- // }
-
- // delete nbt.display?.Lore;
-
- // console.dir(nbt, { depth: Infinity });
- // console.log('===========');
-
- /* if (nbt?.display && nbt.display.Name !== item.displayname)
- console.log(`${path_} display name mismatch: ${mcToAnsi(nbt.display.Name)} != ${mcToAnsi(item.displayname)}`);
-
- if (nbt?.ExtraAttributes && nbt?.ExtraAttributes.id !== item.internalname)
- console.log(`${path_} internal name mismatch: ${mcToAnsi(nbt?.ExtraAttributes.id)} != ${mcToAnsi(item.internalname)}`); */
-
- // console.log('===========');
-
- console.log(mcToAnsi(item.displayname));
- console.log(item.lore.map((l) => mcToAnsi(l)).join('\n'));
-
- /* const keys = [
- 'itemid',
- 'displayname',
- 'nbttag',
- 'damage',
- 'lore',
- 'recipe',
- 'internalname',
- 'modver',
- 'infoType',
- 'info',
- 'crafttext',
- 'vanilla',
- 'useneucraft',
- 'slayer_req',
- 'clickcommand',
- 'x',
- 'y',
- 'z',
- 'island',
- 'recipes',
- 'parent',
- 'noseal'
- ];
-
- Object.keys(item).forEach((k) => {
- if (!keys.includes(k)) throw new Error(`Unknown key: ${k}`);
- });
-
- if (
- 'slayer_req' in item &&
- !new Array(10).flatMap((_, i) => ['WOLF', 'BLAZE', 'EMAN'].map((e) => e + (i + 1)).includes(item.slayer_req!))
- )
- throw new Error(`Unknown slayer req: ${item.slayer_req!}`); */
-
- /* console.log('=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-'); */
-}
diff --git a/src/lib/common/util/Moderation.ts b/src/lib/common/util/Moderation.ts
deleted file mode 100644
index 60e32c0..0000000
--- a/src/lib/common/util/Moderation.ts
+++ /dev/null
@@ -1,556 +0,0 @@
-import {
- ActivePunishment,
- ActivePunishmentType,
- baseMuteResponse,
- colors,
- emojis,
- format,
- Guild as GuildDB,
- humanizeDuration,
- ModLog,
- permissionsResponse,
- type ModLogType,
- type ValueOf
-} from '#lib';
-import assert from 'assert/strict';
-import {
- ActionRowBuilder,
- ButtonBuilder,
- ButtonStyle,
- Client,
- EmbedBuilder,
- PermissionFlagsBits,
- type Guild,
- type GuildMember,
- type GuildMemberResolvable,
- type GuildResolvable,
- type Snowflake,
- type UserResolvable
-} from 'discord.js';
-
-enum punishMap {
- 'warned' = 'warn',
- 'muted' = 'mute',
- 'unmuted' = 'unmute',
- 'kicked' = 'kick',
- 'banned' = 'ban',
- 'unbanned' = 'unban',
- 'timedout' = 'timeout',
- 'untimedout' = 'untimeout',
- 'blocked' = 'block',
- 'unblocked' = 'unblock'
-}
-enum reversedPunishMap {
- 'warn' = 'warned',
- 'mute' = 'muted',
- 'unmute' = 'unmuted',
- 'kick' = 'kicked',
- 'ban' = 'banned',
- 'unban' = 'unbanned',
- 'timeout' = 'timedout',
- 'untimeout' = 'untimedout',
- 'block' = 'blocked',
- 'unblock' = 'unblocked'
-}
-
-/**
- * Checks if a moderator can perform a moderation action on another user.
- * @param moderator The person trying to perform the action.
- * @param victim The person getting punished.
- * @param type The type of punishment - used to format the response.
- * @param checkModerator Whether or not to check if the victim is a moderator.
- * @param force Override permissions checks.
- * @returns `true` if the moderator can perform the action otherwise a reason why they can't.
- */
-export async function permissionCheck(
- moderator: GuildMember,
- victim: GuildMember,
- type:
- | 'mute'
- | 'unmute'
- | 'warn'
- | 'kick'
- | 'ban'
- | 'unban'
- | 'add a punishment role to'
- | 'remove a punishment role from'
- | 'block'
- | 'unblock'
- | 'timeout'
- | 'untimeout',
- checkModerator = true,
- force = false
-): Promise<true | string> {
- if (force) return true;
-
- // If the victim is not in the guild anymore it will be undefined
- if ((!victim || !victim.guild) && !['ban', 'unban'].includes(type)) return true;
-
- if (moderator.guild.id !== victim.guild.id) {
- throw new Error('moderator and victim not in same guild');
- }
-
- const isOwner = moderator.guild.ownerId === moderator.id;
- if (moderator.id === victim.id && !type.startsWith('un')) {
- return `${emojis.error} You cannot ${type} yourself.`;
- }
- if (
- moderator.roles.highest.position <= victim.roles.highest.position &&
- !isOwner &&
- !(type.startsWith('un') && moderator.id === victim.id)
- ) {
- return `${emojis.error} You cannot ${type} **${victim.user.tag}** because they have higher or equal role hierarchy as you do.`;
- }
- if (
- victim.roles.highest.position >= victim.guild.members.me!.roles.highest.position &&
- !(type.startsWith('un') && moderator.id === victim.id)
- ) {
- return `${emojis.error} You cannot ${type} **${victim.user.tag}** because they have higher or equal role hierarchy as I do.`;
- }
- if (
- checkModerator &&
- victim.permissions.has(PermissionFlagsBits.ManageMessages) &&
- !(type.startsWith('un') && moderator.id === victim.id)
- ) {
- if (await moderator.guild.hasFeature('modsCanPunishMods')) {
- return true;
- } else {
- return `${emojis.error} You cannot ${type} **${victim.user.tag}** because they are a moderator.`;
- }
- }
- return true;
-}
-
-/**
- * Performs permission checks that are required in order to (un)mute a member.
- * @param guild The guild to check the mute permissions in.
- * @returns A {@link MuteResponse} or true if nothing failed.
- */
-export async function checkMutePermissions(
- guild: Guild
-): Promise<ValueOf<typeof baseMuteResponse> | ValueOf<typeof permissionsResponse> | true> {
- if (!guild.members.me!.permissions.has('ManageRoles')) return permissionsResponse.MISSING_PERMISSIONS;
- const muteRoleID = await guild.getSetting('muteRole');
- if (!muteRoleID) return baseMuteResponse.NO_MUTE_ROLE;
- const muteRole = guild.roles.cache.get(muteRoleID);
- if (!muteRole) return baseMuteResponse.MUTE_ROLE_INVALID;
- if (muteRole.position >= guild.members.me!.roles.highest.position || muteRole.managed)
- return baseMuteResponse.MUTE_ROLE_NOT_MANAGEABLE;
-
- return true;
-}
-
-/**
- * Creates a modlog entry for a punishment.
- * @param options Options for creating a modlog entry.
- * @param getCaseNumber Whether or not to get the case number of the entry.
- * @returns An object with the modlog and the case number.
- */
-export async function createModLogEntry(
- options: CreateModLogEntryOptions,
- getCaseNumber = false
-): Promise<{ log: ModLog | null; caseNum: number | null }> {
- const user = (await options.client.utils.resolveNonCachedUser(options.user))!.id;
- const moderator = (await options.client.utils.resolveNonCachedUser(options.moderator))!.id;
- const guild = options.client.guilds.resolveId(options.guild)!;
-
- return createModLogEntrySimple(
- {
- ...options,
- user: user,
- moderator: moderator,
- guild: guild
- },
- getCaseNumber
- );
-}
-
-/**
- * Creates a modlog entry with already resolved ids.
- * @param options Options for creating a modlog entry.
- * @param getCaseNumber Whether or not to get the case number of the entry.
- * @returns An object with the modlog and the case number.
- */
-export async function createModLogEntrySimple(
- options: SimpleCreateModLogEntryOptions,
- getCaseNumber = false
-): Promise<{ log: ModLog | null; caseNum: number | null }> {
- // If guild does not exist create it so the modlog can reference a guild.
- await GuildDB.findOrCreate({
- where: { id: options.guild },
- defaults: { id: options.guild }
- });
-
- const modLogEntry = ModLog.build({
- type: options.type,
- user: options.user,
- moderator: options.moderator,
- reason: options.reason,
- duration: options.duration ? options.duration : undefined,
- guild: options.guild,
- pseudo: options.pseudo ?? false,
- evidence: options.evidence,
- hidden: options.hidden ?? false
- });
- const saveResult: ModLog | null = await modLogEntry.save().catch(async (e) => {
- await options.client.utils.handleError('createModLogEntry', e);
- return null;
- });
-
- if (!getCaseNumber) return { log: saveResult, caseNum: null };
-
- const caseNum = (
- await ModLog.findAll({ where: { type: options.type, user: options.user, guild: options.guild, hidden: false } })
- )?.length;
- return { log: saveResult, caseNum };
-}
-
-/**
- * Creates a punishment entry.
- * @param options Options for creating the punishment entry.
- * @returns The database entry, or null if no entry is created.
- */
-export async function createPunishmentEntry(options: CreatePunishmentEntryOptions): Promise<ActivePunishment | null> {
- const expires = options.duration ? new Date(+new Date() + options.duration ?? 0) : undefined;
- const user = (await options.client.utils.resolveNonCachedUser(options.user))!.id;
- const guild = options.client.guilds.resolveId(options.guild)!;
- const type = findTypeEnum(options.type)!;
-
- const entry = ActivePunishment.build(
- options.extraInfo
- ? { user, type, guild, expires, modlog: options.modlog, extraInfo: options.extraInfo }
- : { user, type, guild, expires, modlog: options.modlog }
- );
- return await entry.save().catch(async (e) => {
- await options.client.utils.handleError('createPunishmentEntry', e);
- return null;
- });
-}
-
-/**
- * Destroys a punishment entry.
- * @param options Options for destroying the punishment entry.
- * @returns Whether or not the entry was destroyed.
- */
-export async function removePunishmentEntry(options: RemovePunishmentEntryOptions): Promise<boolean> {
- const user = await options.client.utils.resolveNonCachedUser(options.user);
- const guild = options.client.guilds.resolveId(options.guild);
- const type = findTypeEnum(options.type);
-
- if (!user || !guild) return false;
-
- let success = true;
-
- const entries = await ActivePunishment.findAll({
- // finding all cases of a certain type incase there were duplicates or something
- where: options.extraInfo
- ? { user: user.id, guild: guild, type, extraInfo: options.extraInfo }
- : { user: user.id, guild: guild, type }
- }).catch(async (e) => {
- await options.client.utils.handleError('removePunishmentEntry', e);
- success = false;
- });
- if (entries) {
- const promises = entries.map(async (entry) =>
- entry.destroy().catch(async (e) => {
- await options.client.utils.handleError('removePunishmentEntry', e);
- success = false;
- })
- );
-
- await Promise.all(promises);
- }
- return success;
-}
-
-/**
- * Returns the punishment type enum for the given type.
- * @param type The type of the punishment.
- * @returns The punishment type enum.
- */
-function findTypeEnum(type: 'mute' | 'ban' | 'role' | 'block') {
- const typeMap = {
- ['mute']: ActivePunishmentType.MUTE,
- ['ban']: ActivePunishmentType.BAN,
- ['role']: ActivePunishmentType.ROLE,
- ['block']: ActivePunishmentType.BLOCK
- };
- return typeMap[type];
-}
-
-export function punishmentToPresentTense(punishment: PunishmentTypeDM): PunishmentTypePresent {
- return punishMap[punishment];
-}
-
-export function punishmentToPastTense(punishment: PunishmentTypePresent): PunishmentTypeDM {
- return reversedPunishMap[punishment];
-}
-
-/**
- * Notifies the specified user of their punishment.
- * @param options Options for notifying the user.
- * @returns Whether or not the dm was successfully sent.
- */
-export async function punishDM(options: PunishDMOptions): Promise<boolean> {
- const ending = await options.guild.getSetting('punishmentEnding');
- const dmEmbed =
- ending && ending.length && options.sendFooter
- ? new EmbedBuilder().setDescription(ending).setColor(colors.newBlurple)
- : undefined;
-
- const appealsEnabled = !!(
- (await options.guild.hasFeature('punishmentAppeals')) && (await options.guild.getLogChannel('appeals'))
- );
-
- let content = `You have been ${options.punishment} `;
- if (options.punishment.includes('blocked')) {
- assert(options.channel);
- content += `from <#${options.channel}> `;
- }
- content += `in ${format.input(options.guild.name)} `;
- if (options.duration !== null && options.duration !== undefined)
- content += options.duration ? `for ${humanizeDuration(options.duration)} ` : 'permanently ';
- const reason = options.reason?.trim() ? options.reason?.trim() : 'No reason provided';
- content += `for ${format.input(reason)}.`;
-
- let components;
- if (appealsEnabled && options.modlog)
- components = [
- new ActionRowBuilder<ButtonBuilder>({
- components: [
- new ButtonBuilder({
- customId: `appeal;${punishmentToPresentTense(options.punishment)};${
- options.guild.id
- };${options.client.users.resolveId(options.user)};${options.modlog}`,
- style: ButtonStyle.Primary,
- label: 'Appeal'
- }).toJSON()
- ]
- })
- ];
-
- const dmSuccess = await options.client.users
- .send(options.user, {
- content,
- embeds: dmEmbed ? [dmEmbed] : undefined,
- components
- })
- .catch(() => false);
- return !!dmSuccess;
-}
-
-interface BaseCreateModLogEntryOptions extends BaseOptions {
- /**
- * The type of modlog entry.
- */
- type: ModLogType;
-
- /**
- * The reason for the punishment.
- */
- reason: string | undefined | null;
-
- /**
- * The duration of the punishment.
- */
- duration?: number;
-
- /**
- * Whether the punishment is a pseudo punishment.
- */
- pseudo?: boolean;
-
- /**
- * The evidence for the punishment.
- */
- evidence?: string;
-
- /**
- * Makes the modlog entry hidden.
- */
- hidden?: boolean;
-}
-
-/**
- * Options for creating a modlog entry.
- */
-export interface CreateModLogEntryOptions extends BaseCreateModLogEntryOptions {
- /**
- * The client.
- */
- client: Client;
-
- /**
- * The user that a modlog entry is created for.
- */
- user: GuildMemberResolvable;
-
- /**
- * The moderator that created the modlog entry.
- */
- moderator: GuildMemberResolvable;
-
- /**
- * The guild that the punishment is created for.
- */
- guild: GuildResolvable;
-}
-
-/**
- * Simple options for creating a modlog entry.
- */
-export interface SimpleCreateModLogEntryOptions extends BaseCreateModLogEntryOptions {
- /**
- * The user that a modlog entry is created for.
- */
- user: Snowflake;
-
- /**
- * The moderator that created the modlog entry.
- */
- moderator: Snowflake;
-
- /**
- * The guild that the punishment is created for.
- */
- guild: Snowflake;
-}
-
-/**
- * Options for creating a punishment entry.
- */
-export interface CreatePunishmentEntryOptions extends BaseOptions {
- /**
- * The type of punishment.
- */
- type: 'mute' | 'ban' | 'role' | 'block';
-
- /**
- * The user that the punishment is created for.
- */
- user: GuildMemberResolvable;
-
- /**
- * The length of time the punishment lasts for.
- */
- duration: number | undefined;
-
- /**
- * The guild that the punishment is created for.
- */
- guild: GuildResolvable;
-
- /**
- * The id of the modlog that is linked to the punishment entry.
- */
- modlog: string;
-
- /**
- * Extra information for the punishment. The role for role punishments and the channel for blocks.
- */
- extraInfo?: Snowflake;
-}
-
-/**
- * Options for removing a punishment entry.
- */
-export interface RemovePunishmentEntryOptions extends BaseOptions {
- /**
- * The type of punishment.
- */
- type: 'mute' | 'ban' | 'role' | 'block';
-
- /**
- * The user that the punishment is destroyed for.
- */
- user: GuildMemberResolvable;
-
- /**
- * The guild that the punishment was in.
- */
- guild: GuildResolvable;
-
- /**
- * Extra information for the punishment. The role for role punishments and the channel for blocks.
- */
- extraInfo?: Snowflake;
-}
-
-/**
- * Options for sending a user a punishment dm.
- */
-export interface PunishDMOptions extends BaseOptions {
- /**
- * The modlog case id so the user can make an appeal.
- */
- modlog?: string;
-
- /**
- * The guild that the punishment is taking place in.
- */
- guild: Guild;
-
- /**
- * The user that is being punished.
- */
- user: UserResolvable;
-
- /**
- * The punishment that the user has received.
- */
- punishment: PunishmentTypeDM;
-
- /**
- * The reason the user's punishment.
- */
- reason?: string;
-
- /**
- * The duration of the punishment.
- */
- duration?: number;
-
- /**
- * Whether or not to send the guild's punishment footer with the dm.
- * @default true
- */
- sendFooter: boolean;
-
- /**
- * The channel that the user was (un)blocked from.
- */
- channel?: Snowflake;
-}
-
-interface BaseOptions {
- /**
- * The client.
- */
- client: Client;
-}
-
-export type PunishmentTypeDM =
- | 'warned'
- | 'muted'
- | 'unmuted'
- | 'kicked'
- | 'banned'
- | 'unbanned'
- | 'timedout'
- | 'untimedout'
- | 'blocked'
- | 'unblocked';
-
-export type PunishmentTypePresent =
- | 'warn'
- | 'mute'
- | 'unmute'
- | 'kick'
- | 'ban'
- | 'unban'
- | 'timeout'
- | 'untimeout'
- | 'block'
- | 'unblock';
-
-export type AppealButtonId = `appeal;${PunishmentTypePresent};${Snowflake};${Snowflake};${string}`;
diff --git a/src/lib/extensions/discord-akairo/BushArgumentTypeCaster.ts b/src/lib/extensions/discord-akairo/BushArgumentTypeCaster.ts
deleted file mode 100644
index def7ad6..0000000
--- a/src/lib/extensions/discord-akairo/BushArgumentTypeCaster.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { type CommandMessage } from '#lib';
-
-export type BushArgumentTypeCaster<R = unknown> = (message: CommandMessage, phrase: string) => R;
diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts
deleted file mode 100644
index 9ca02a2..0000000
--- a/src/lib/extensions/discord-akairo/BushClient.ts
+++ /dev/null
@@ -1,586 +0,0 @@
-import {
- abbreviatedNumber,
- contentWithDuration,
- discordEmoji,
- duration,
- durationSeconds,
- globalUser,
- messageLink,
- permission,
- roleWithDuration,
- snowflake
-} from '#args';
-import { BushClientEvents, emojis, formatError, inspect } from '#lib';
-import { patch, type PatchedElements } from '@notenoughupdates/events-intercept';
-import * as Sentry from '@sentry/node';
-import {
- AkairoClient,
- ArgumentTypeCaster,
- ContextMenuCommandHandler,
- version as akairoVersion,
- type ArgumentPromptData,
- type OtherwiseContentSupplier
-} from 'discord-akairo';
-import {
- ActivityType,
- GatewayIntentBits,
- MessagePayload,
- Options,
- Partials,
- Structures,
- version as discordJsVersion,
- type Awaitable,
- type If,
- type InteractionReplyOptions,
- type Message,
- type MessageEditOptions,
- type MessageOptions,
- type ReplyMessageOptions,
- type Snowflake,
- type UserResolvable,
- type WebhookEditMessageOptions
-} from 'discord.js';
-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 { fileURLToPath } from 'url';
-import type { Config } from '../../../../config/Config.js';
-import { tinyColor } from '../../../arguments/tinyColor.js';
-import UpdateCacheTask from '../../../tasks/cache/updateCache.js';
-import UpdateStatsTask from '../../../tasks/feature/updateStats.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 { AllowedMentions } from '../../utils/AllowedMentions.js';
-import { BushCache } from '../../utils/BushCache.js';
-import { BushClientUtils } from '../../utils/BushClientUtils.js';
-import { BushLogger } from '../../utils/BushLogger.js';
-import { ExtendedGuild } from '../discord.js/ExtendedGuild.js';
-import { ExtendedGuildMember } from '../discord.js/ExtendedGuildMember.js';
-import { ExtendedMessage } from '../discord.js/ExtendedMessage.js';
-import { ExtendedUser } from '../discord.js/ExtendedUser.js';
-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 {
- /** The ID of the owner(s). */
- ownerID: Snowflake | Snowflake[];
- /** The ID of the superUser(s). */
- superUserID: Snowflake | Snowflake[];
- /** Whether or not the client is ready. */
- customReady: boolean;
- /** The configuration for the client. */
- readonly config: Config;
- /** Stats for the client. */
- readonly stats: BushStats;
- /** The handler for the bot's listeners. */
- readonly listenerHandler: BushListenerHandler;
- /** The handler for the bot's command inhibitors. */
- readonly inhibitorHandler: BushInhibitorHandler;
- /** The handler for the bot's commands. */
- readonly commandHandler: BushCommandHandler;
- /** The handler for the bot's tasks. */
- readonly taskHandler: BushTaskHandler;
- /** The handler for the bot's context menu commands. */
- readonly contextMenuCommandHandler: ContextMenuCommandHandler;
- /** The database connection for this instance of the bot (production, beta, or development). */
- readonly instanceDB: SequelizeType;
- /** The database connection that is shared between all instances of the bot. */
- readonly sharedDB: SequelizeType;
- /** A custom logging system for the bot. */
- readonly logger: BushLogger;
- /** Cached global and guild database data. */
- readonly cache: BushCache;
- /** Sentry error reporting for the bot. */
- readonly sentry: typeof Sentry;
- /** Manages most aspects of the highlight command */
- readonly highlightManager: HighlightManager;
- /** The perspective api */
- perspective: any;
- /** Client utilities. */
- readonly utils: BushClientUtils;
- /** A custom logging system for the bot. */
- get console(): BushLogger;
- on<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this;
- once<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this;
- emit<K extends keyof BushClientEvents>(event: K, ...args: BushClientEvents[K]): boolean;
- off<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this;
- removeAllListeners<K extends keyof BushClientEvents>(event?: K): this;
- /**
- * Checks if a user is the owner of this bot.
- * @param user - User to check.
- */
- isOwner(user: UserResolvable): boolean;
- /**
- * Checks if a user is a super user of this bot.
- * @param user - User to check.
- */
- isSuperUser(user: UserResolvable): boolean;
- }
-}
-
-export type ReplyMessageType = string | MessagePayload | ReplyMessageOptions;
-export type EditMessageType = string | MessageEditOptions | MessagePayload;
-export type SlashSendMessageType = string | MessagePayload | InteractionReplyOptions;
-export type SlashEditMessageType = string | MessagePayload | WebhookEditMessageOptions;
-export type SendMessageType = string | MessagePayload | MessageOptions;
-
-const rl = readline.createInterface({
- input: process.stdin,
- output: process.stdout,
- terminal: false
-});
-
-const __dirname = path.dirname(fileURLToPath(import.meta.url));
-
-/**
- * The main hub for interacting with the Discord API.
- */
-export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Ready> {
- public declare ownerID: Snowflake[];
- public declare superUserID: Snowflake[];
-
- /**
- * Whether or not the client is ready.
- */
- public override customReady = false;
-
- /**
- * Stats for the client.
- */
- public override readonly stats: BushStats = { cpu: undefined, commandsUsed: 0n, slashCommandsUsed: 0n };
-
- /**
- * The handler for the bot's listeners.
- */
- public override readonly listenerHandler: BushListenerHandler;
-
- /**
- * The handler for the bot's command inhibitors.
- */
- public override readonly inhibitorHandler: BushInhibitorHandler;
-
- /**
- * The handler for the bot's commands.
- */
- public override readonly commandHandler: BushCommandHandler;
-
- /**
- * The handler for the bot's tasks.
- */
- public override readonly taskHandler: BushTaskHandler;
-
- /**
- * The handler for the bot's context menu commands.
- */
- public override readonly contextMenuCommandHandler: ContextMenuCommandHandler;
-
- /**
- * The database connection for this instance of the bot (production, beta, or development).
- */
- public override readonly instanceDB: SequelizeType;
-
- /**
- * The database connection that is shared between all instances of the bot.
- */
- public override readonly sharedDB: SequelizeType;
-
- /**
- * A custom logging system for the bot.
- */
- public override readonly logger: BushLogger = new BushLogger(this);
-
- /**
- * Cached global and guild database data.
- */
- public override readonly cache = new BushCache();
-
- /**
- * Sentry error reporting for the bot.
- */
- public override readonly sentry!: typeof Sentry;
-
- /**
- * Manages most aspects of the highlight command
- */
- public override readonly highlightManager: HighlightManager = new HighlightManager(this);
-
- /**
- * The perspective api
- */
- public override perspective: any;
-
- /**
- * Client utilities.
- */
- public override readonly utils: BushClientUtils = new BushClientUtils(this);
-
- /**
- * @param config The configuration for the client.
- */
- public constructor(
- /**
- * The configuration for the client.
- */
- public override readonly config: Config
- ) {
- super({
- ownerID: config.owners,
- intents: Object.keys(GatewayIntentBits)
- .map((i) => (typeof i === 'string' ? GatewayIntentBits[i as keyof typeof GatewayIntentBits] : i))
- .reduce((acc, p) => acc | p, 0),
- partials: Object.keys(Partials).map((p) => Partials[p as keyof typeof Partials]),
- presence: {
- activities: [{ name: 'Beep Boop', type: ActivityType.Watching }],
- status: 'online'
- },
- allowedMentions: AllowedMentions.none(), // no mentions by default
- makeCache: Options.cacheWithLimits({}),
- failIfNotExists: false,
- rest: { api: 'https://canary.discord.com/api' }
- });
- patch(this);
-
- this.token = config.token as If<Ready, string, string | null>;
-
- /* =-=-= handlers =-=-= */
- this.listenerHandler = new BushListenerHandler(this, {
- directory: path.join(__dirname, '..', '..', '..', 'listeners'),
- automateCategories: true
- });
- this.inhibitorHandler = new BushInhibitorHandler(this, {
- directory: path.join(__dirname, '..', '..', '..', 'inhibitors'),
- automateCategories: true
- });
- this.taskHandler = new BushTaskHandler(this, {
- directory: path.join(__dirname, '..', '..', '..', 'tasks'),
- automateCategories: true
- });
-
- const modify = async (
- message: Message,
- text: string | MessagePayload | MessageOptions | OtherwiseContentSupplier,
- data: ArgumentPromptData,
- replaceError: boolean
- ) => {
- const ending = '\n\n Type **cancel** to cancel the command';
- const options = typeof text === 'function' ? await text(message, data) : text;
- const search = '{error}',
- replace = emojis.error;
-
- if (typeof options === 'string') return (replaceError ? options.replace(search, replace) : options) + ending;
-
- if (options instanceof MessagePayload) {
- if (options.options.content) {
- if (replaceError) options.options.content = options.options.content.replace(search, replace);
- options.options.content += ending;
- }
- } else if (options.content) {
- if (replaceError) options.content = options.content.replace(search, replace);
- options.content += ending;
- }
- return options;
- };
-
- this.commandHandler = new BushCommandHandler(this, {
- directory: path.join(__dirname, '..', '..', '..', 'commands'),
- prefix: async ({ guild }: Message) => {
- if (this.config.isDevelopment) return 'dev ';
- if (!guild) return this.config.prefix;
- const prefix = await guild.getSetting('prefix');
- return (prefix ?? this.config.prefix) as string;
- },
- allowMention: true,
- handleEdits: true,
- commandUtil: true,
- commandUtilLifetime: 300_000, // 5 minutes
- argumentDefaults: {
- prompt: {
- start: 'Placeholder argument prompt. **If you see this please tell my developers**.',
- retry: 'Placeholder failed argument prompt. **If you see this please tell my developers**.',
- modifyStart: (message, text, data) => modify(message, text, data, false),
- modifyRetry: (message, text, data) => modify(message, text, data, true),
- timeout: ':hourglass: You took too long the command has been cancelled.',
- ended: 'You exceeded the maximum amount of tries the command has been cancelled',
- cancel: 'The command has been cancelled',
- retries: 3,
- time: 3e4
- },
- otherwise: ''
- },
- automateCategories: false,
- autoRegisterSlashCommands: true,
- skipBuiltInPostInhibitors: true,
- aliasReplacement: /-/g
- });
- this.contextMenuCommandHandler = new ContextMenuCommandHandler(this, {
- directory: path.join(__dirname, '..', '..', '..', 'context-menu-commands'),
- automateCategories: true
- });
-
- /* =-=-= databases =-=-= */
- const sharedDBOptions: SequelizeOptions = {
- username: this.config.db.username,
- password: this.config.db.password,
- dialect: 'postgres',
- host: this.config.db.host,
- port: this.config.db.port,
- logging: this.config.logging.db ? (sql) => this.logger.debug(sql) : false,
- timezone: 'America/New_York'
- };
- this.instanceDB = new Sequelize({
- ...sharedDBOptions,
- database: this.config.isDevelopment ? 'bushbot-dev' : this.config.isBeta ? 'bushbot-beta' : 'bushbot'
- });
- this.sharedDB = new Sequelize({
- ...sharedDBOptions,
- database: 'bushbot-shared'
- });
-
- this.sentry = Sentry;
- }
-
- /**
- * A custom logging system for the bot.
- */
- public override get console(): BushLogger {
- return this.logger;
- }
-
- /**
- * Extends discord.js structures before the client is instantiated.
- */
- public static extendStructures(): void {
- Structures.extend('GuildMember', () => ExtendedGuildMember);
- Structures.extend('Guild', () => ExtendedGuild);
- Structures.extend('Message', () => ExtendedMessage);
- Structures.extend('User', () => ExtendedUser);
- }
-
- /**
- * Initializes the bot.
- */
- public async init() {
- if (parseInt(process.versions.node.split('.')[0]) < 17) {
- void (await this.console.error('version', `Please use node <<v17.x.x>>, not <<${process.version}>>.`, false));
- process.exit(2);
- }
-
- this.setMaxListeners(20);
-
- this.perspective = await google.discoverAPI<any>('https://commentanalyzer.googleapis.com/$discovery/rest?version=v1alpha1');
-
- this.commandHandler.useInhibitorHandler(this.inhibitorHandler);
- this.commandHandler.useListenerHandler(this.listenerHandler);
- this.commandHandler.useTaskHandler(this.taskHandler);
- this.commandHandler.useContextMenuCommandHandler(this.contextMenuCommandHandler);
- this.commandHandler.ignorePermissions = this.config.owners;
- this.commandHandler.ignoreCooldown = [...new Set([...this.config.owners, ...this.cache.shared.superUsers])];
- const emitters: Emitters = {
- client: this,
- commandHandler: this.commandHandler,
- inhibitorHandler: this.inhibitorHandler,
- listenerHandler: this.listenerHandler,
- taskHandler: this.taskHandler,
- contextMenuCommandHandler: this.contextMenuCommandHandler,
- process,
- stdin: rl,
- gateway: this.ws,
- rest: this.rest,
- ws: this.ws
- };
- this.listenerHandler.setEmitters(emitters);
- this.commandHandler.resolver.addTypes({
- duration: <ArgumentTypeCaster>duration,
- contentWithDuration: <ArgumentTypeCaster>contentWithDuration,
- permission: <ArgumentTypeCaster>permission,
- snowflake: <ArgumentTypeCaster>snowflake,
- discordEmoji: <ArgumentTypeCaster>discordEmoji,
- roleWithDuration: <ArgumentTypeCaster>roleWithDuration,
- abbreviatedNumber: <ArgumentTypeCaster>abbreviatedNumber,
- durationSeconds: <ArgumentTypeCaster>durationSeconds,
- globalUser: <ArgumentTypeCaster>globalUser,
- messageLink: <ArgumentTypeCaster>messageLink,
- tinyColor: <ArgumentTypeCaster>tinyColor
- });
-
- this.sentry.setTag('process', process.pid.toString());
- this.sentry.setTag('discord.js', discordJsVersion);
- this.sentry.setTag('discord-akairo', akairoVersion);
- void this.logger.success('startup', `Successfully connected to <<Sentry>>.`, false);
-
- // loads all the handlers
- const handlers = {
- commands: this.commandHandler,
- contextMenuCommands: this.contextMenuCommandHandler,
- listeners: this.listenerHandler,
- inhibitors: this.inhibitorHandler,
- tasks: this.taskHandler
- };
- const handlerPromises = Object.entries(handlers).map(([handlerName, handler]) =>
- handler
- .loadAll()
- .then(() => {
- void this.logger.success('startup', `Successfully loaded <<${handlerName}>>.`, false);
- })
- .catch((e) => {
- void this.logger.error('startup', `Unable to load loader <<${handlerName}>> with error:\n${formatError(e)}`, false);
- if (process.argv.includes('dry')) process.exit(1);
- })
- );
- await Promise.allSettled(handlerPromises);
- }
-
- /**
- * Connects to the database, initializes models, and creates tables if they do not exist.
- */
- public async dbPreInit() {
- try {
- await this.instanceDB.authenticate();
- GuildDB.initModel(this.instanceDB, this);
- ModLog.initModel(this.instanceDB);
- ActivePunishment.initModel(this.instanceDB);
- Level.initModel(this.instanceDB);
- StickyRole.initModel(this.instanceDB);
- Reminder.initModel(this.instanceDB);
- Highlight.initModel(this.instanceDB);
- await this.instanceDB.sync({ alter: true }); // Sync all tables to fix everything if updated
- await this.console.success('startup', `Successfully connected to <<instance database>>.`, false);
- } catch (e) {
- await this.console.error(
- 'startup',
- `Failed to connect to <<instance database>> with error:\n${inspect(e, { colors: true, depth: 1 })}`,
- false
- );
- process.exit(2);
- }
- try {
- await this.sharedDB.authenticate();
- Stat.initModel(this.sharedDB);
- Global.initModel(this.sharedDB);
- Shared.initModel(this.sharedDB);
- MemberCount.initModel(this.sharedDB);
- GuildCount.initModel(this.sharedDB);
- await this.sharedDB.sync({
- // Sync all tables to fix everything if updated
- // if another instance restarts we don't want to overwrite new changes made in development
- alter: this.config.isDevelopment
- });
- await this.console.success('startup', `Successfully connected to <<shared database>>.`, false);
- } catch (e) {
- await this.console.error(
- 'startup',
- `Failed to connect to <<shared database>> with error:\n${inspect(e, { colors: true, depth: 1 })}`,
- false
- );
- process.exit(2);
- }
- }
-
- /**
- * Starts the bot
- */
- public async start() {
- this.intercept('ready', async (arg, done) => {
- const promises = this.guilds.cache
- .filter((g) => g.large)
- .map((guild) => {
- return guild.members.fetch();
- });
- await Promise.all(promises);
- this.customReady = true;
- this.taskHandler.startAll();
- return done(null, `intercepted ${arg}`);
- });
-
- try {
- await this.highlightManager.syncCache();
- await UpdateCacheTask.init(this);
- void this.console.success('startup', `Successfully created <<cache>>.`, false);
- const stats = await UpdateStatsTask.init(this);
- this.stats.commandsUsed = stats.commandsUsed;
- this.stats.slashCommandsUsed = stats.slashCommandsUsed;
- await this.login(this.token!);
- } catch (e) {
- await this.console.error('start', inspect(e, { colors: true, depth: 1 }), false);
- process.exit(1);
- }
- }
-
- /**
- * Logs out, terminates the connection to Discord, and destroys the client.
- */
- public override destroy(relogin = false): void | Promise<string> {
- super.destroy();
- if (relogin) {
- return this.login(this.token!);
- }
- }
-
- public override isOwner(user: UserResolvable): boolean {
- return this.config.owners.includes(this.users.resolveId(user!)!);
- }
-
- public override isSuperUser(user: UserResolvable): boolean {
- const userID = this.users.resolveId(user)!;
- return this.cache.shared.superUsers.includes(userID) || this.config.owners.includes(userID);
- }
-}
-
-export interface BushClient<Ready extends boolean = boolean> extends EventEmitter, PatchedElements, AkairoClient<Ready> {
- on<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this;
- once<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this;
- emit<K extends keyof BushClientEvents>(event: K, ...args: BushClientEvents[K]): boolean;
- off<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this;
- removeAllListeners<K extends keyof BushClientEvents>(event?: K): this;
-}
-
-/**
- * Various statistics
- */
-export interface BushStats {
- /**
- * The average cpu usage of the bot from the past 60 seconds.
- */
- cpu: number | undefined;
-
- /**
- * The total number of times any command has been used.
- */
- commandsUsed: bigint;
-
- /**
- * The total number of times any slash command has been used.
- */
- slashCommandsUsed: bigint;
-}
-
-export interface Emitters {
- client: BushClient;
- commandHandler: BushClient['commandHandler'];
- inhibitorHandler: BushClient['inhibitorHandler'];
- listenerHandler: BushClient['listenerHandler'];
- taskHandler: BushClient['taskHandler'];
- contextMenuCommandHandler: BushClient['contextMenuCommandHandler'];
- process: NodeJS.Process;
- stdin: readline.Interface;
- gateway: BushClient['ws'];
- rest: BushClient['rest'];
- ws: BushClient['ws'];
-}
diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts
deleted file mode 100644
index dc2295f..0000000
--- a/src/lib/extensions/discord-akairo/BushCommand.ts
+++ /dev/null
@@ -1,586 +0,0 @@
-import { type DiscordEmojiInfo, type RoleWithDuration } from '#args';
-import {
- type BushArgumentTypeCaster,
- type BushClient,
- type BushCommandHandler,
- type BushInhibitor,
- type BushListener,
- type BushTask,
- type ParsedDuration
-} from '#lib';
-import {
- ArgumentMatch,
- Command,
- CommandUtil,
- type AkairoApplicationCommandAutocompleteOption,
- type AkairoApplicationCommandChannelOptionData,
- type AkairoApplicationCommandChoicesData,
- type AkairoApplicationCommandNonOptionsData,
- type AkairoApplicationCommandNumericOptionData,
- type AkairoApplicationCommandOptionData,
- type AkairoApplicationCommandSubCommandData,
- type AkairoApplicationCommandSubGroupData,
- type ArgumentOptions,
- type ArgumentType,
- type ArgumentTypeCaster,
- type BaseArgumentType,
- type CommandOptions,
- type ContextMenuCommand,
- type MissingPermissionSupplier,
- type SlashOption,
- type SlashResolveType
-} from 'discord-akairo';
-import {
- Message,
- User,
- type ApplicationCommandOptionChoiceData,
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- type ApplicationCommandOptionType,
- type PermissionResolvable,
- type PermissionsString,
- type Snowflake
-} from 'discord.js';
-import _ from 'lodash';
-import { SlashMessage } from './SlashMessage.js';
-
-export interface OverriddenBaseArgumentType extends BaseArgumentType {
- commandAlias: BushCommand | null;
- command: BushCommand | null;
- inhibitor: BushInhibitor | null;
- listener: BushListener | null;
- task: BushTask | null;
- contextMenuCommand: ContextMenuCommand | null;
-}
-
-export interface BaseBushArgumentType extends OverriddenBaseArgumentType {
- duration: number | null;
- contentWithDuration: ParsedDuration;
- permission: PermissionsString | null;
- snowflake: Snowflake | null;
- discordEmoji: DiscordEmojiInfo | null;
- roleWithDuration: RoleWithDuration | null;
- abbreviatedNumber: number | null;
- globalUser: User | null;
- messageLink: Message | null;
- durationSeconds: number | null;
- tinyColor: string | null;
-}
-
-export type BushArgumentType = keyof BaseBushArgumentType | RegExp;
-
-interface BaseBushArgumentOptions extends Omit<ArgumentOptions, 'type' | 'prompt'>, ExtraArgumentOptions {
- id: string;
- description: string;
-
- /**
- * The message sent for the prompt and the slash command description.
- */
- prompt?: string;
-
- /**
- * The message set for the retry prompt.
- */
- retry?: string;
-
- /**
- * Whether or not the argument is optional.
- */
- optional?: boolean;
-
- /**
- * The type used for slash commands. Set to false to disable this argument for slash commands.
- */
- slashType: AkairoApplicationCommandOptionData['type'] | false;
-
- /**
- * Allows you to get a discord resolved object
- *
- * ex. get the resolved member object when the type is {@link ApplicationCommandOptionType.User User}
- */
- slashResolve?: SlashResolveType;
-
- /**
- * The choices of the option for the user to pick from
- */
- choices?: ApplicationCommandOptionChoiceData[];
-
- /**
- * Whether the option is an autocomplete option
- */
- autocomplete?: boolean;
-
- /**
- * When the option type is channel, the allowed types of channels that can be selected
- */
- channelTypes?: AkairoApplicationCommandChannelOptionData['channelTypes'];
-
- /**
- * The minimum value for an {@link ApplicationCommandOptionType.Integer Integer} or {@link ApplicationCommandOptionType.Number Number} option
- */
- minValue?: number;
-
- /**
- * The maximum value for an {@link ApplicationCommandOptionType.Integer Integer} or {@link ApplicationCommandOptionType.Number Number} option
- */
- maxValue?: number;
-}
-
-interface ExtraArgumentOptions {
- /**
- * Restrict this argument to only slash or only text commands.
- */
- only?: 'slash' | 'text';
-
- /**
- * Readable type for the help command.
- */
- readableType?: string;
-
- /**
- * Whether the argument is only accessible to the owners.
- * @default false
- */
- ownerOnly?: boolean;
-
- /**
- * Whether the argument is only accessible to the super users.
- * @default false
- */
- superUserOnly?: boolean;
-}
-
-export interface BushArgumentOptions extends BaseBushArgumentOptions {
- /**
- * The type that the argument should be cast to.
- * - `string` does not cast to any type.
- * - `lowercase` makes the input lowercase.
- * - `uppercase` makes the input uppercase.
- * - `charCodes` transforms the input to an array of char codes.
- * - `number` casts to a number.
- * - `integer` casts to an integer.
- * - `bigint` casts to a big integer.
- * - `url` casts to an `URL` object.
- * - `date` casts to a `Date` object.
- * - `color` casts a hex code to an integer.
- * - `commandAlias` tries to resolve to a command from an alias.
- * - `command` matches the ID of a command.
- * - `inhibitor` matches the ID of an inhibitor.
- * - `listener` matches the ID of a listener.
- *
- * Possible Discord-related types.
- * These types can be plural (add an 's' to the end) and a collection of matching objects will be used.
- * - `user` tries to resolve to a user.
- * - `member` tries to resolve to a member.
- * - `relevant` tries to resolve to a relevant user, works in both guilds and DMs.
- * - `channel` tries to resolve to a channel.
- * - `textChannel` tries to resolve to a text channel.
- * - `voiceChannel` tries to resolve to a voice channel.
- * - `stageChannel` tries to resolve to a stage channel.
- * - `threadChannel` tries to resolve a thread channel.
- * - `role` tries to resolve to a role.
- * - `emoji` tries to resolve to a custom emoji.
- * - `guild` tries to resolve to a guild.
- * - `permission` tries to resolve to a permissions.
- *
- * Other Discord-related types:
- * - `message` tries to fetch a message from an ID within the channel.
- * - `guildMessage` tries to fetch a message from an ID within the guild.
- * - `relevantMessage` is a combination of the above, works in both guilds and DMs.
- * - `invite` tries to fetch an invite object from a link.
- * - `userMention` matches a mention of a user.
- * - `memberMention` matches a mention of a guild member.
- * - `channelMention` matches a mention of a channel.
- * - `roleMention` matches a mention of a role.
- * - `emojiMention` matches a mention of an emoji.
- *
- * Misc:
- * - `duration` tries to parse duration in milliseconds
- * - `contentWithDuration` tries to parse duration in milliseconds and returns the remaining content with the duration
- * removed
- */
- type?: BushArgumentType | (keyof BaseBushArgumentType)[] | BushArgumentTypeCaster;
-}
-
-export interface CustomBushArgumentOptions extends BaseBushArgumentOptions {
- /**
- * An array of strings can be used to restrict input to only those strings, case insensitive.
- * The array can also contain an inner array of strings, for aliases.
- * If so, the first entry of the array will be used as the final argument.
- *
- * A regular expression can also be used.
- * The evaluated argument will be an object containing the `match` and `matches` if global.
- */
- customType?: (string | string[])[] | RegExp | string | null;
-}
-
-export type BushMissingPermissionSupplier = (message: CommandMessage | SlashMessage) => Promise<any> | any;
-
-interface ExtendedCommandOptions {
- /**
- * Whether the command is hidden from the help command.
- */
- hidden?: boolean;
-
- /**
- * The channels the command is limited to run in.
- */
- restrictedChannels?: Snowflake[];
-
- /**
- * The guilds the command is limited to run in.
- */
- restrictedGuilds?: Snowflake[];
-
- /**
- * Show how to use the command.
- */
- usage: string[];
-
- /**
- * Examples for how to use the command.
- */
- examples: string[];
-
- /**
- * A fake command, completely hidden from the help command.
- */
- pseudo?: boolean;
-
- /**
- * Allow this command to be run in channels that are blacklisted.
- */
- bypassChannelBlacklist?: boolean;
-
- /**
- * Use instead of {@link BaseBushCommandOptions.args} when using argument generators or custom slashOptions
- */
- helpArgs?: ArgsInfo[];
-
- /**
- * Extra information about the command, displayed in the help command.
- */
- note?: string;
-}
-
-export interface BaseBushCommandOptions
- extends Omit<CommandOptions, 'userPermissions' | 'clientPermissions' | 'args'>,
- ExtendedCommandOptions {
- /**
- * The description of the command.
- */
- description: string;
-
- /**
- * The arguments for the command.
- */
- args?: BushArgumentOptions[] & CustomBushArgumentOptions[];
-
- category: string;
-
- /**
- * Permissions required by the client to run this command.
- */
- clientPermissions: bigint | bigint[] | BushMissingPermissionSupplier;
-
- /**
- * Permissions required by the user to run this command.
- */
- userPermissions: bigint | bigint[] | BushMissingPermissionSupplier;
-
- /**
- * Whether the argument is only accessible to the owners.
- */
- ownerOnly?: boolean;
-
- /**
- * Whether the argument is only accessible to the super users.
- */
- superUserOnly?: boolean;
-}
-
-export type BushCommandOptions = Omit<BaseBushCommandOptions, 'helpArgs'> | Omit<BaseBushCommandOptions, 'args'>;
-
-export interface ArgsInfo {
- /**
- * The name of the argument.
- */
- name: string;
-
- /**
- * The description of the argument.
- */
- description: string;
-
- /**
- * Whether the argument is optional.
- * @default false
- */
- optional?: boolean;
-
- /**
- * Whether or not the argument has autocomplete enabled.
- * @default false
- */
- autocomplete?: boolean;
-
- /**
- * Whether the argument is restricted a certain command.
- * @default 'slash & text'
- */
- only?: 'slash & text' | 'slash' | 'text';
-
- /**
- * The method that arguments are matched for text commands.
- * @default 'phrase'
- */
- match?: ArgumentMatch;
-
- /**
- * The readable type of the argument.
- */
- type: string;
-
- /**
- * If {@link match} is 'flag' or 'option', these are the flags that are matched
- * @default []
- */
- flag?: string[];
-
- /**
- * Whether the argument is only accessible to the owners.
- * @default false
- */
- ownerOnly?: boolean;
-
- /**
- * Whether the argument is only accessible to the super users.
- * @default false
- */
- superUserOnly?: boolean;
-}
-
-export abstract class BushCommand extends Command {
- public declare client: BushClient;
- public declare handler: BushCommandHandler;
- public declare description: string;
-
- /**
- * Show how to use the command.
- */
- public usage: string[];
-
- /**
- * Examples for how to use the command.
- */
- public examples: string[];
-
- /**
- * The options sent to the constructor
- */
- public options: BushCommandOptions;
-
- /**
- * The options sent to the super call
- */
- public parsedOptions: CommandOptions;
-
- /**
- * The channels the command is limited to run in.
- */
- public restrictedChannels: Snowflake[] | undefined;
-
- /**
- * The guilds the command is limited to run in.
- */
- public restrictedGuilds: Snowflake[] | undefined;
-
- /**
- * Whether the command is hidden from the help command.
- */
- public hidden: boolean;
-
- /**
- * A fake command, completely hidden from the help command.
- */
- public pseudo: boolean;
-
- /**
- * Allow this command to be run in channels that are blacklisted.
- */
- public bypassChannelBlacklist: boolean;
-
- /**
- * Info about the arguments for the help command.
- */
- public argsInfo?: ArgsInfo[];
-
- /**
- * Extra information about the command, displayed in the help command.
- */
- public note?: string;
-
- public constructor(id: string, options: BushCommandOptions) {
- const options_ = options as BaseBushCommandOptions;
-
- if (options_.args && typeof options_.args !== 'function') {
- options_.args.forEach((_, index: number) => {
- if ('customType' in (options_.args?.[index] ?? {})) {
- if (!options_.args![index]['type']) options_.args![index]['type'] = options_.args![index]['customType']! as any;
- delete options_.args![index]['customType'];
- }
- });
- }
-
- const newOptions: Partial<CommandOptions & ExtendedCommandOptions> = {};
- for (const _key in options_) {
- const key = _key as keyof typeof options_; // you got to love typescript
- if (key === 'args' && 'args' in options_ && typeof options_.args === 'object') {
- const newTextArgs: (ArgumentOptions & ExtraArgumentOptions)[] = [];
- const newSlashArgs: SlashOption[] = [];
- for (const arg of options_.args) {
- if (arg.only !== 'slash' && !options_.slashOnly) {
- const newArg: ArgumentOptions & ExtraArgumentOptions = {};
- if ('default' in arg) newArg.default = arg.default;
- if ('description' in arg) newArg.description = arg.description;
- if ('flag' in arg) newArg.flag = arg.flag;
- if ('id' in arg) newArg.id = arg.id;
- if ('index' in arg) newArg.index = arg.index;
- if ('limit' in arg) newArg.limit = arg.limit;
- if ('match' in arg) newArg.match = arg.match;
- if ('modifyOtherwise' in arg) newArg.modifyOtherwise = arg.modifyOtherwise;
- if ('multipleFlags' in arg) newArg.multipleFlags = arg.multipleFlags;
- if ('otherwise' in arg) newArg.otherwise = arg.otherwise;
- if ('prompt' in arg || 'retry' in arg || 'optional' in arg) {
- newArg.prompt = {};
- if ('prompt' in arg) newArg.prompt.start = arg.prompt;
- if ('retry' in arg) newArg.prompt.retry = arg.retry;
- if ('optional' in arg) newArg.prompt.optional = arg.optional;
- }
- if ('type' in arg) newArg.type = arg.type as ArgumentType | ArgumentTypeCaster;
- if ('unordered' in arg) newArg.unordered = arg.unordered;
- if ('ownerOnly' in arg) newArg.ownerOnly = arg.ownerOnly;
- if ('superUserOnly' in arg) newArg.superUserOnly = arg.superUserOnly;
- newTextArgs.push(newArg);
- }
- if (
- arg.only !== 'text' &&
- !('slashOptions' in options_) &&
- (options_.slash || options_.slashOnly) &&
- arg.slashType !== false
- ) {
- const newArg: {
- [key in SlashOptionKeys]?: any;
- } = {
- name: arg.id,
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- description: arg.prompt || arg.description || 'No description provided.',
- type: arg.slashType
- };
- if ('slashResolve' in arg) newArg.resolve = arg.slashResolve;
- if ('autocomplete' in arg) newArg.autocomplete = arg.autocomplete;
- if ('channelTypes' in arg) newArg.channelTypes = arg.channelTypes;
- if ('choices' in arg) newArg.choices = arg.choices;
- if ('minValue' in arg) newArg.minValue = arg.minValue;
- if ('maxValue' in arg) newArg.maxValue = arg.maxValue;
- newArg.required = 'optional' in arg ? !arg.optional : true;
- newSlashArgs.push(newArg as SlashOption);
- }
- }
- if (newTextArgs.length > 0) newOptions.args = newTextArgs;
- if (newSlashArgs.length > 0) newOptions.slashOptions = options_.slashOptions ?? newSlashArgs;
- } else if (key === 'clientPermissions' || key === 'userPermissions') {
- newOptions[key] = options_[key] as PermissionResolvable | PermissionResolvable[] | MissingPermissionSupplier;
- } else {
- newOptions[key] = options_[key];
- }
- }
-
- super(id, newOptions);
-
- if (options_.args ?? options_.helpArgs) {
- const argsInfo: ArgsInfo[] = [];
- const combined = (options_.args ?? options_.helpArgs)!.map((arg) => {
- const norm = options_.args
- ? options_.args.find((_arg) => _arg.id === ('id' in arg ? arg.id : arg.name)) ?? ({} as BushArgumentOptions)
- : ({} as BushArgumentOptions);
- const help = options_.helpArgs
- ? options_.helpArgs.find((_arg) => _arg.name === ('id' in arg ? arg.id : arg.name)) ?? ({} as ArgsInfo)
- : ({} as ArgsInfo);
- return { ...norm, ...help };
- });
-
- for (const arg of combined) {
- const name = _.camelCase('id' in arg ? arg.id : arg.name),
- description = arg.description || '*No description provided.*',
- optional = arg.optional ?? false,
- autocomplete = arg.autocomplete ?? false,
- only = arg.only ?? 'slash & text',
- match = arg.match ?? 'phrase',
- type = match === 'flag' ? 'flag' : arg.readableType ?? arg.type ?? 'string',
- flag = arg.flag ? (Array.isArray(arg.flag) ? arg.flag : [arg.flag]) : [],
- ownerOnly = arg.ownerOnly ?? false,
- superUserOnly = arg.superUserOnly ?? false;
-
- argsInfo.push({ name, description, optional, autocomplete, only, match, type, flag, ownerOnly, superUserOnly });
- }
-
- this.argsInfo = argsInfo;
- }
-
- this.description = options_.description;
- this.usage = options_.usage;
- this.examples = options_.examples;
- this.options = options_;
- this.parsedOptions = newOptions;
- this.hidden = !!options_.hidden;
- this.restrictedChannels = options_.restrictedChannels;
- this.restrictedGuilds = options_.restrictedGuilds;
- this.pseudo = !!options_.pseudo;
- this.bypassChannelBlacklist = !!options_.bypassChannelBlacklist;
- this.note = options_.note;
- }
-
- /**
- * Executes the command.
- * @param message - Message that triggered the command.
- * @param args - Evaluated arguments.
- */
- public abstract override exec(message: CommandMessage, args: any): any;
- /**
- * Executes the command.
- * @param message - Message that triggered the command.
- * @param args - Evaluated arguments.
- */
- public abstract override exec(message: CommandMessage | SlashMessage, args: any): any;
-}
-
-type SlashOptionKeys =
- | keyof AkairoApplicationCommandSubGroupData
- | keyof AkairoApplicationCommandNonOptionsData
- | keyof AkairoApplicationCommandChannelOptionData
- | keyof AkairoApplicationCommandChoicesData
- | keyof AkairoApplicationCommandAutocompleteOption
- | keyof AkairoApplicationCommandNumericOptionData
- | keyof AkairoApplicationCommandSubCommandData;
-
-interface PseudoArguments extends BaseBushArgumentType {
- boolean: boolean;
- flag: boolean;
- regex: { match: RegExpMatchArray; matches: RegExpExecArray[] };
-}
-
-export type ArgType<T extends keyof PseudoArguments> = NonNullable<PseudoArguments[T]>;
-export type OptArgType<T extends keyof PseudoArguments> = PseudoArguments[T];
-
-/**
- * `util` is always defined for messages after `'all'` inhibitors
- */
-export type CommandMessage = Message & {
- /**
- * Extra properties applied to the Discord.js message object.
- * Utilities for command responding.
- * Available on all messages after 'all' inhibitors and built-in inhibitors (bot, client).
- * Not all properties of the util are available, depending on the input.
- * */
- util: CommandUtil<Message>;
-};
diff --git a/src/lib/extensions/discord-akairo/BushCommandHandler.ts b/src/lib/extensions/discord-akairo/BushCommandHandler.ts
deleted file mode 100644
index da49af9..0000000
--- a/src/lib/extensions/discord-akairo/BushCommandHandler.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { type BushCommand, type CommandMessage, type SlashMessage } from '#lib';
-import { CommandHandler, type Category, type CommandHandlerEvents, type CommandHandlerOptions } from 'discord-akairo';
-import { type Collection, type Message, type PermissionsString } from 'discord.js';
-
-export type BushCommandHandlerOptions = CommandHandlerOptions;
-
-export interface BushCommandHandlerEvents extends CommandHandlerEvents {
- commandBlocked: [message: CommandMessage, command: BushCommand, reason: string];
- commandBreakout: [message: CommandMessage, command: BushCommand, /* no util */ breakMessage: Message];
- commandCancelled: [message: CommandMessage, command: BushCommand, /* no util */ retryMessage?: Message];
- commandFinished: [message: CommandMessage, command: BushCommand, args: any, returnValue: any];
- commandInvalid: [message: CommandMessage, command: BushCommand];
- commandLocked: [message: CommandMessage, command: BushCommand];
- commandStarted: [message: CommandMessage, command: BushCommand, args: any];
- cooldown: [message: CommandMessage | SlashMessage, command: BushCommand, remaining: number];
- error: [error: Error, message: /* no util */ Message, command?: BushCommand];
- inPrompt: [message: /* no util */ Message];
- load: [command: BushCommand, isReload: boolean];
- messageBlocked: [message: /* no util */ Message | CommandMessage | SlashMessage, reason: string];
- messageInvalid: [message: CommandMessage];
- missingPermissions: [message: CommandMessage, command: BushCommand, type: 'client' | 'user', missing: PermissionsString[]];
- remove: [command: BushCommand];
- slashBlocked: [message: SlashMessage, command: BushCommand, reason: string];
- slashError: [error: Error, message: SlashMessage, command: BushCommand];
- slashFinished: [message: SlashMessage, command: BushCommand, args: any, returnValue: any];
- slashMissingPermissions: [message: SlashMessage, command: BushCommand, type: 'client' | 'user', missing: PermissionsString[]];
- slashStarted: [message: SlashMessage, command: BushCommand, args: any];
-}
-
-export class BushCommandHandler extends CommandHandler {
- public declare modules: Collection<string, BushCommand>;
- public declare categories: Collection<string, Category<string, BushCommand>>;
-}
-
-export interface BushCommandHandler extends CommandHandler {
- findCommand(name: string): BushCommand;
-}
diff --git a/src/lib/extensions/discord-akairo/BushInhibitor.ts b/src/lib/extensions/discord-akairo/BushInhibitor.ts
deleted file mode 100644
index be396cf..0000000
--- a/src/lib/extensions/discord-akairo/BushInhibitor.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { type BushCommand, type CommandMessage, type SlashMessage } from '#lib';
-import { Inhibitor } from 'discord-akairo';
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-import { Message } from 'discord.js';
-
-export abstract class BushInhibitor extends Inhibitor {
- /**
- * Checks if message should be blocked.
- * A return value of true will block the message.
- * If returning a Promise, a resolved value of true will block the message.
- *
- * **Note:** `'all'` type inhibitors do not have {@link Message.util} defined.
- *
- * @param message - Message being handled.
- * @param command - Command to check.
- */
- public abstract override exec(message: CommandMessage, command: BushCommand): any;
- public abstract override exec(message: CommandMessage | SlashMessage, command: BushCommand): any;
-}
diff --git a/src/lib/extensions/discord-akairo/BushInhibitorHandler.ts b/src/lib/extensions/discord-akairo/BushInhibitorHandler.ts
deleted file mode 100644
index 5e4fb6c..0000000
--- a/src/lib/extensions/discord-akairo/BushInhibitorHandler.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { InhibitorHandler } from 'discord-akairo';
-
-export class BushInhibitorHandler extends InhibitorHandler {}
diff --git a/src/lib/extensions/discord-akairo/BushListener.ts b/src/lib/extensions/discord-akairo/BushListener.ts
deleted file mode 100644
index 6917641..0000000
--- a/src/lib/extensions/discord-akairo/BushListener.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { Listener } from 'discord-akairo';
-
-export abstract class BushListener extends Listener {}
diff --git a/src/lib/extensions/discord-akairo/BushListenerHandler.ts b/src/lib/extensions/discord-akairo/BushListenerHandler.ts
deleted file mode 100644
index 9c3e4af..0000000
--- a/src/lib/extensions/discord-akairo/BushListenerHandler.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { ListenerHandler } from 'discord-akairo';
-
-export class BushListenerHandler extends ListenerHandler {}
diff --git a/src/lib/extensions/discord-akairo/BushTask.ts b/src/lib/extensions/discord-akairo/BushTask.ts
deleted file mode 100644
index 1b70c88..0000000
--- a/src/lib/extensions/discord-akairo/BushTask.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { Task } from 'discord-akairo';
-
-export abstract class BushTask extends Task {}
diff --git a/src/lib/extensions/discord-akairo/BushTaskHandler.ts b/src/lib/extensions/discord-akairo/BushTaskHandler.ts
deleted file mode 100644
index 6535abb..0000000
--- a/src/lib/extensions/discord-akairo/BushTaskHandler.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { TaskHandler } from 'discord-akairo';
-
-export class BushTaskHandler extends TaskHandler {}
diff --git a/src/lib/extensions/discord-akairo/SlashMessage.ts b/src/lib/extensions/discord-akairo/SlashMessage.ts
deleted file mode 100644
index 0a6669b..0000000
--- a/src/lib/extensions/discord-akairo/SlashMessage.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { AkairoMessage } from 'discord-akairo';
-
-export class SlashMessage extends AkairoMessage {}
diff --git a/src/lib/extensions/discord.js/BushClientEvents.ts b/src/lib/extensions/discord.js/BushClientEvents.ts
deleted file mode 100644
index 22bae65..0000000
--- a/src/lib/extensions/discord.js/BushClientEvents.ts
+++ /dev/null
@@ -1,200 +0,0 @@
-import type {
- BanResponse,
- CommandMessage,
- Guild as GuildDB,
- GuildSettings
-} from '#lib';
-import type { AkairoClientEvents } from 'discord-akairo';
-import type {
- ButtonInteraction,
- Collection,
- Guild,
- GuildMember,
- GuildTextBasedChannel,
- Message,
- ModalSubmitInteraction,
- Role,
- SelectMenuInteraction,
- Snowflake,
- User
-} from 'discord.js';
-
-export interface BushClientEvents extends AkairoClientEvents {
- bushBan: [
- victim: GuildMember | User,
- moderator: User,
- guild: Guild,
- reason: string | undefined,
- caseID: string,
- duration: number,
- dmSuccess?: boolean,
- evidence?: string
- ];
- bushBlock: [
- victim: GuildMember,
- moderator: User,
- guild: Guild,
- reason: string | undefined,
- caseID: string,
- duration: number,
- dmSuccess: boolean,
- channel: GuildTextBasedChannel,
- evidence?: string
- ];
- bushKick: [
- victim: GuildMember,
- moderator: User,
- guild: Guild,
- reason: string | undefined,
- caseID: string,
- dmSuccess: boolean,
- evidence?: string
- ];
- bushMute: [
- victim: GuildMember,
- moderator: User,
- guild: Guild,
- reason: string | undefined,
- caseID: string,
- duration: number,
- dmSuccess: boolean,
- evidence?: string
- ];
- bushPunishRole: [
- victim: GuildMember,
- moderator: User,
- guild: Guild,
- reason: string | undefined,
- caseID: string,
- duration: number,
- role: Role,
- evidence?: string
- ];
- bushPunishRoleRemove: [
- victim: GuildMember,
- moderator: User,
- guild: Guild,
- reason: string | undefined,
- caseID: string,
- role: Role,
- evidence?: string
- ];
- bushPurge: [
- moderator: User,
- guild: Guild,
- channel: GuildTextBasedChannel,
- messages: Collection<Snowflake, Message>
- ];
- bushRemoveTimeout: [
- victim: GuildMember,
- moderator: User,
- guild: Guild,
- reason: string | undefined,
- caseID: string,
- dmSuccess: boolean,
- evidence?: string
- ];
- bushTimeout: [
- victim: GuildMember,
- moderator: User,
- guild: Guild,
- reason: string | undefined,
- caseID: string,
- duration: number,
- dmSuccess: boolean,
- evidence?: string
- ];
- bushUnban: [
- victim: User,
- moderator: User,
- guild: Guild,
- reason: string | undefined,
- caseID: string,
- dmSuccess: boolean,
- evidence?: string
- ];
- bushUnblock: [
- victim: GuildMember | User,
- moderator: User,
- guild: Guild,
- reason: string | undefined,
- caseID: string,
- dmSuccess: boolean,
- channel: GuildTextBasedChannel,
- evidence?: string
- ];
- bushUnmute: [
- victim: GuildMember,
- moderator: User,
- guild: Guild,
- reason: string | undefined,
- caseID: string,
- dmSuccess: boolean,
- evidence?: string
- ];
- bushUpdateModlog: [
- moderator: GuildMember,
- modlogID: string,
- key: 'evidence' | 'hidden',
- oldModlog: string | boolean,
- newModlog: string | boolean
- ];
- bushUpdateSettings: [
- setting: Setting,
- guild: Guild,
- oldValue: GuildDB[Setting],
- newValue: GuildDB[Setting],
- moderator?: GuildMember
- ];
- bushWarn: [
- victim: GuildMember,
- moderator: User,
- guild: Guild,
- reason: string | undefined,
- caseID: string,
- dmSuccess: boolean,
- evidence?: string
- ];
- bushLevelUpdate: [
- member: GuildMember,
- oldLevel: number,
- newLevel: number,
- currentXp: number,
- message: CommandMessage
- ];
- bushLockdown: [
- moderator: GuildMember,
- reason: string | undefined,
- channelsSuccessMap: Collection<Snowflake, boolean>,
- all?: boolean
- ];
- bushUnlockdown: [
- moderator: GuildMember,
- reason: string | undefined,
- channelsSuccessMap: Collection<Snowflake, boolean>,
- all?: boolean
- ];
- massBan: [
- moderator: GuildMember,
- guild: Guild,
- reason: string | undefined,
- results: Collection<Snowflake, BanResponse>
- ];
- massEvidence: [
- moderator: GuildMember,
- guild: Guild,
- evidence: string,
- lines: string[]
- ];
- /* components */
- button: [button: ButtonInteraction];
- selectMenu: [selectMenu: SelectMenuInteraction];
- modal: [modal: ModalSubmitInteraction];
-}
-
-type Setting =
- | GuildSettings
- | 'enabledFeatures'
- | 'blacklistedChannels'
- | 'blacklistedUsers'
- | 'disabledCommands';
diff --git a/src/lib/extensions/discord.js/ExtendedGuild.ts b/src/lib/extensions/discord.js/ExtendedGuild.ts
deleted file mode 100644
index 3dce7ca..0000000
--- a/src/lib/extensions/discord.js/ExtendedGuild.ts
+++ /dev/null
@@ -1,916 +0,0 @@
-import {
- AllowedMentions,
- banResponse,
- colors,
- dmResponse,
- emojis,
- permissionsResponse,
- punishmentEntryRemove,
- type BanResponse,
- type GuildFeatures,
- type GuildLogType,
- type GuildModel
-} from '#lib';
-import assert from 'assert/strict';
-import {
- AttachmentBuilder,
- AttachmentPayload,
- Collection,
- Guild,
- JSONEncodable,
- Message,
- MessageType,
- PermissionFlagsBits,
- SnowflakeUtil,
- ThreadChannel,
- type APIMessage,
- type GuildMember,
- type GuildMemberResolvable,
- type GuildTextBasedChannel,
- type MessageOptions,
- type MessagePayload,
- type NewsChannel,
- type Snowflake,
- type TextChannel,
- type User,
- type UserResolvable,
- type VoiceChannel,
- type Webhook,
- type WebhookMessageOptions
-} from 'discord.js';
-import _ from 'lodash';
-import * as Moderation from '../../common/util/Moderation.js';
-import { Guild as GuildDB } from '../../models/instance/Guild.js';
-import { ModLogType } from '../../models/instance/ModLog.js';
-import { addOrRemoveFromArray } from '../../utils/BushUtils.js';
-
-declare module 'discord.js' {
- export interface Guild {
- /**
- * Checks if the guild has a certain custom feature.
- * @param feature The feature to check for
- */
- hasFeature(feature: GuildFeatures): Promise<boolean>;
- /**
- * Adds a custom feature to the guild.
- * @param feature The feature to add
- * @param moderator The moderator responsible for adding a feature
- */
- addFeature(feature: GuildFeatures, moderator?: GuildMember): Promise<GuildDB['enabledFeatures']>;
- /**
- * Removes a custom feature from the guild.
- * @param feature The feature to remove
- * @param moderator The moderator responsible for removing a feature
- */
- removeFeature(feature: GuildFeatures, moderator?: GuildMember): Promise<GuildDB['enabledFeatures']>;
- /**
- * Makes a custom feature the opposite of what it was before
- * @param feature The feature to toggle
- * @param moderator The moderator responsible for toggling a feature
- */
- toggleFeature(feature: GuildFeatures, moderator?: GuildMember): Promise<GuildDB['enabledFeatures']>;
- /**
- * Fetches a custom setting for the guild
- * @param setting The setting to get
- */
- getSetting<K extends keyof GuildModel>(setting: K): Promise<GuildModel[K]>;
- /**
- * Sets a custom setting for the guild
- * @param setting The setting to change
- * @param value The value to change the setting to
- * @param moderator The moderator to responsible for changing the setting
- */
- setSetting<K extends Exclude<keyof GuildModel, 'id'>>(
- setting: K,
- value: GuildModel[K],
- moderator?: GuildMember
- ): Promise<GuildModel>;
- /**
- * Get a the log channel configured for a certain log type.
- * @param logType The type of log channel to get.
- * @returns Either the log channel or undefined if not configured.
- */
- getLogChannel(logType: GuildLogType): Promise<TextChannel | undefined>;
- /**
- * Sends a message to the guild's specified logging channel
- * @param logType The corresponding channel that the message will be sent to
- * @param message The parameters for {@link BushTextChannel.send}
- */
- sendLogChannel(logType: GuildLogType, message: string | MessagePayload | MessageOptions): Promise<Message | null | undefined>;
- /**
- * Sends a formatted error message in a guild's error log channel
- * @param title The title of the error embed
- * @param message The description of the error embed
- */
- error(title: string, message: string): Promise<void>;
- /**
- * Bans a user, dms them, creates a mod log entry, and creates a punishment entry.
- * @param options Options for banning the user.
- * @returns A string status message of the ban.
- */
- bushBan(options: GuildBushBanOptions): Promise<BanResponse>;
- /**
- * {@link bushBan} with less resolving and checks
- * @param options Options for banning the user.
- * @returns A string status message of the ban.
- * **Preconditions:**
- * - {@link me} has the `BanMembers` permission
- * **Warning:**
- * - Doesn't emit bushBan Event
- */
- massBanOne(options: GuildMassBanOneOptions): Promise<BanResponse>;
- /**
- * Unbans a user, dms them, creates a mod log entry, and destroys the punishment entry.
- * @param options Options for unbanning the user.
- * @returns A status message of the unban.
- */
- bushUnban(options: GuildBushUnbanOptions): Promise<UnbanResponse>;
- /**
- * Denies send permissions in specified channels
- * @param options The options for locking down the guild
- */
- lockdown(options: LockdownOptions): Promise<LockdownResponse>;
- quote(rawQuote: APIMessage, channel: GuildTextBasedChannel): Promise<Message | null>;
- }
-}
-
-/**
- * Represents a guild (or a server) on Discord.
- * <info>It's recommended to see if a guild is available before performing operations or reading data from it. You can
- * check this with {@link ExtendedGuild.available}.</info>
- */
-export class ExtendedGuild extends Guild {
- /**
- * Checks if the guild has a certain custom feature.
- * @param feature The feature to check for
- */
- public override async hasFeature(feature: GuildFeatures): Promise<boolean> {
- const features = await this.getSetting('enabledFeatures');
- return features.includes(feature);
- }
-
- /**
- * Adds a custom feature to the guild.
- * @param feature The feature to add
- * @param moderator The moderator responsible for adding a feature
- */
- public override async addFeature(feature: GuildFeatures, moderator?: GuildMember): Promise<GuildModel['enabledFeatures']> {
- const features = await this.getSetting('enabledFeatures');
- const newFeatures = addOrRemoveFromArray('add', features, feature);
- return (await this.setSetting('enabledFeatures', newFeatures, moderator)).enabledFeatures;
- }
-
- /**
- * Removes a custom feature from the guild.
- * @param feature The feature to remove
- * @param moderator The moderator responsible for removing a feature
- */
- public override async removeFeature(feature: GuildFeatures, moderator?: GuildMember): Promise<GuildModel['enabledFeatures']> {
- const features = await this.getSetting('enabledFeatures');
- const newFeatures = addOrRemoveFromArray('remove', features, feature);
- return (await this.setSetting('enabledFeatures', newFeatures, moderator)).enabledFeatures;
- }
-
- /**
- * Makes a custom feature the opposite of what it was before
- * @param feature The feature to toggle
- * @param moderator The moderator responsible for toggling a feature
- */
- public override async toggleFeature(feature: GuildFeatures, moderator?: GuildMember): Promise<GuildModel['enabledFeatures']> {
- return (await this.hasFeature(feature))
- ? await this.removeFeature(feature, moderator)
- : await this.addFeature(feature, moderator);
- }
-
- /**
- * Fetches a custom setting for the guild
- * @param setting The setting to get
- */
- public override async getSetting<K extends keyof GuildModel>(setting: K): Promise<GuildModel[K]> {
- return (
- this.client.cache.guilds.get(this.id)?.[setting] ??
- ((await GuildDB.findByPk(this.id)) ?? GuildDB.build({ id: this.id }))[setting]
- );
- }
-
- /**
- * Sets a custom setting for the guild
- * @param setting The setting to change
- * @param value The value to change the setting to
- * @param moderator The moderator to responsible for changing the setting
- */
- public override async setSetting<K extends Exclude<keyof GuildModel, 'id'>>(
- setting: K,
- value: GuildDB[K],
- moderator?: GuildMember
- ): Promise<GuildDB> {
- const row = (await GuildDB.findByPk(this.id)) ?? GuildDB.build({ id: this.id });
- const oldValue = row[setting] as GuildDB[K];
- row[setting] = value;
- this.client.cache.guilds.set(this.id, row.toJSON() as GuildDB);
- this.client.emit('bushUpdateSettings', setting, this, oldValue, row[setting], moderator);
- return await row.save();
- }
-
- /**
- * Get a the log channel configured for a certain log type.
- * @param logType The type of log channel to get.
- * @returns Either the log channel or undefined if not configured.
- */
- public override async getLogChannel(logType: GuildLogType): Promise<TextChannel | undefined> {
- const channelId = (await this.getSetting('logChannels'))[logType];
- if (!channelId) return undefined;
- return (
- (this.channels.cache.get(channelId) as TextChannel | undefined) ??
- ((await this.channels.fetch(channelId)) as TextChannel | null) ??
- undefined
- );
- }
-
- /**
- * Sends a message to the guild's specified logging channel
- * @param logType The corresponding channel that the message will be sent to
- * @param message The parameters for {@link BushTextChannel.send}
- */
- public override async sendLogChannel(
- logType: GuildLogType,
- message: string | MessagePayload | MessageOptions
- ): Promise<Message | null | undefined> {
- const logChannel = await this.getLogChannel(logType);
- if (!logChannel || !logChannel.isTextBased()) return;
- if (
- !logChannel
- .permissionsFor(this.members.me!.id)
- ?.has([PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages, PermissionFlagsBits.EmbedLinks])
- )
- return;
-
- return await logChannel.send(message).catch(() => null);
- }
-
- /**
- * Sends a formatted error message in a guild's error log channel
- * @param title The title of the error embed
- * @param message The description of the error embed
- */
- public override async error(title: string, message: string): Promise<void> {
- void this.client.console.info(_.camelCase(title), message.replace(/\*\*(.*?)\*\*/g, '<<$1>>'));
- void this.sendLogChannel('error', { embeds: [{ title: title, description: message, color: colors.error }] });
- }
-
- /**
- * Bans a user, dms them, creates a mod log entry, and creates a punishment entry.
- * @param options Options for banning the user.
- * @returns A string status message of the ban.
- */
- public override async bushBan(options: GuildBushBanOptions): Promise<BanResponse> {
- // checks
- if (!this.members.me!.permissions.has(PermissionFlagsBits.BanMembers)) return banResponse.MISSING_PERMISSIONS;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const user = await this.client.utils.resolveNonCachedUser(options.user);
- const moderator = this.client.users.resolve(options.moderator ?? this.client.user!);
- if (!user || !moderator) return banResponse.CANNOT_RESOLVE_USER;
-
- if ((await this.bans.fetch()).has(user.id)) return banResponse.ALREADY_BANNED;
-
- const ret = await (async () => {
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: options.duration ? ModLogType.TEMP_BAN : ModLogType.PERM_BAN,
- user: user,
- moderator: moderator.id,
- reason: options.reason,
- duration: options.duration,
- guild: this,
- evidence: options.evidence
- });
- if (!modlog) return banResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- // dm user
- dmSuccessEvent = await Moderation.punishDM({
- client: this.client,
- modlog: modlog.id,
- guild: this,
- user: user,
- punishment: 'banned',
- duration: options.duration ?? 0,
- reason: options.reason ?? undefined,
- sendFooter: true
- });
-
- // ban
- const banSuccess = await this.bans
- .create(user?.id ?? options.user, {
- reason: `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`,
- deleteMessageDays: options.deleteDays
- })
- .catch(() => false);
- if (!banSuccess) return banResponse.ACTION_ERROR;
-
- // add punishment entry so they can be unbanned later
- const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
- client: this.client,
- type: 'ban',
- user: user,
- guild: this,
- duration: options.duration,
- modlog: modlog.id
- });
- if (!punishmentEntrySuccess) return banResponse.PUNISHMENT_ENTRY_ADD_ERROR;
-
- if (!dmSuccessEvent) return banResponse.DM_ERROR;
- return banResponse.SUCCESS;
- })();
-
- if (!([banResponse.ACTION_ERROR, banResponse.MODLOG_ERROR, banResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const).includes(ret))
- this.client.emit(
- 'bushBan',
- user,
- moderator,
- this,
- options.reason ?? undefined,
- caseID!,
- options.duration ?? 0,
- dmSuccessEvent,
- options.evidence
- );
- return ret;
- }
-
- /**
- * {@link bushBan} with less resolving and checks
- * @param options Options for banning the user.
- * @returns A string status message of the ban.
- * **Preconditions:**
- * - {@link me} has the `BanMembers` permission
- * **Warning:**
- * - Doesn't emit bushBan Event
- */
- public override async massBanOne(options: GuildMassBanOneOptions): Promise<BanResponse> {
- if (this.bans.cache.has(options.user)) return banResponse.ALREADY_BANNED;
-
- const ret = await (async () => {
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntrySimple({
- client: this.client,
- type: ModLogType.PERM_BAN,
- user: options.user,
- moderator: options.moderator,
- reason: options.reason,
- duration: 0,
- guild: this.id
- });
- if (!modlog) return banResponse.MODLOG_ERROR;
-
- let dmSuccessEvent: boolean | undefined = undefined;
- // dm user
- if (this.members.cache.has(options.user)) {
- dmSuccessEvent = await Moderation.punishDM({
- client: this.client,
- modlog: modlog.id,
- guild: this,
- user: options.user,
- punishment: 'banned',
- duration: 0,
- reason: options.reason ?? undefined,
- sendFooter: true
- });
- }
-
- // ban
- const banSuccess = await this.bans
- .create(options.user, {
- reason: `${options.moderator} | ${options.reason}`,
- deleteMessageDays: options.deleteDays
- })
- .catch(() => false);
- if (!banSuccess) return banResponse.ACTION_ERROR;
-
- // add punishment entry so they can be unbanned later
- const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
- client: this.client,
- type: 'ban',
- user: options.user,
- guild: this,
- duration: 0,
- modlog: modlog.id
- });
- if (!punishmentEntrySuccess) return banResponse.PUNISHMENT_ENTRY_ADD_ERROR;
-
- if (!dmSuccessEvent) return banResponse.DM_ERROR;
- return banResponse.SUCCESS;
- })();
- return ret;
- }
-
- /**
- * Unbans a user, dms them, creates a mod log entry, and destroys the punishment entry.
- * @param options Options for unbanning the user.
- * @returns A status message of the unban.
- */
- public override async bushUnban(options: GuildBushUnbanOptions): Promise<UnbanResponse> {
- // checks
- if (!this.members.me!.permissions.has(PermissionFlagsBits.BanMembers)) return unbanResponse.MISSING_PERMISSIONS;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const user = await this.client.utils.resolveNonCachedUser(options.user);
- const moderator = this.client.users.resolve(options.moderator ?? this.client.user!);
- if (!user || !moderator) return unbanResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- const bans = await this.bans.fetch();
-
- let notBanned = false;
- if (!bans.has(user.id)) notBanned = true;
-
- const unbanSuccess = await this.bans
- .remove(user, `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`)
- .catch((e) => {
- if (e?.code === 'UNKNOWN_BAN') {
- notBanned = true;
- return true;
- } else return false;
- });
-
- if (notBanned) return unbanResponse.NOT_BANNED;
- if (!unbanSuccess) return unbanResponse.ACTION_ERROR;
-
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: ModLogType.UNBAN,
- user: user.id,
- moderator: moderator.id,
- reason: options.reason,
- guild: this,
- evidence: options.evidence
- });
- if (!modlog) return unbanResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- // remove punishment entry
- const removePunishmentEntrySuccess = await Moderation.removePunishmentEntry({
- client: this.client,
- type: 'ban',
- user: user.id,
- guild: this
- });
- if (!removePunishmentEntrySuccess) return unbanResponse.PUNISHMENT_ENTRY_REMOVE_ERROR;
-
- // dm user
- dmSuccessEvent = await Moderation.punishDM({
- client: this.client,
- guild: this,
- user: user,
- punishment: 'unbanned',
- reason: options.reason ?? undefined,
- sendFooter: false
- });
-
- if (!dmSuccessEvent) return unbanResponse.DM_ERROR;
- return unbanResponse.SUCCESS;
- })();
- if (
- !([unbanResponse.ACTION_ERROR, unbanResponse.MODLOG_ERROR, unbanResponse.PUNISHMENT_ENTRY_REMOVE_ERROR] as const).includes(
- ret
- )
- )
- this.client.emit(
- 'bushUnban',
- user,
- moderator,
- this,
- options.reason ?? undefined,
- caseID!,
- dmSuccessEvent!,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Denies send permissions in specified channels
- * @param options The options for locking down the guild
- */
- public override async lockdown(options: LockdownOptions): Promise<LockdownResponse> {
- if (!options.all && !options.channel) return 'all not chosen and no channel specified';
- const channelIds = options.all ? await this.getSetting('lockdownChannels') : [options.channel!.id];
-
- if (!channelIds.length) return 'no channels configured';
- const mappedChannels = channelIds.map((id) => this.channels.cache.get(id));
-
- const invalidChannels = mappedChannels.filter((c) => c === undefined);
- if (invalidChannels.length) return `invalid channel configured: ${invalidChannels.join(', ')}`;
-
- const moderator = this.members.resolve(options.moderator);
- if (!moderator) return 'moderator not found';
-
- const errors = new Collection<Snowflake, Error>();
- const success = new Collection<Snowflake, boolean>();
- const ret = await (async (): Promise<LockdownResponse> => {
- for (const _channel of mappedChannels) {
- const channel = _channel!;
- if (!channel.isTextBased()) {
- errors.set(channel.id, new Error('wrong channel type'));
- success.set(channel.id, false);
- continue;
- }
- if (!channel.permissionsFor(this.members.me!.id)?.has([PermissionFlagsBits.ManageChannels])) {
- errors.set(channel.id, new Error('client no permission'));
- success.set(channel.id, false);
- continue;
- } else if (!channel.permissionsFor(moderator)?.has([PermissionFlagsBits.ManageChannels])) {
- errors.set(channel.id, new Error('moderator no permission'));
- success.set(channel.id, false);
- continue;
- }
-
- const reason = `[${options.unlock ? 'Unlockdown' : 'Lockdown'}] ${moderator.user.tag} | ${
- options.reason ?? 'No reason provided'
- }`;
-
- const permissionOverwrites = channel.isThread() ? channel.parent!.permissionOverwrites : channel.permissionOverwrites;
- const perms = {
- SendMessagesInThreads: options.unlock ? null : false,
- SendMessages: options.unlock ? null : false
- };
- const permsForMe = {
- [channel.isThread() ? 'SendMessagesInThreads' : 'SendMessages']: options.unlock ? null : true
- }; // so I can send messages in the channel
-
- const changePermSuccess = await permissionOverwrites.edit(this.id, perms, { reason }).catch((e) => e);
- if (changePermSuccess instanceof Error) {
- errors.set(channel.id, changePermSuccess);
- success.set(channel.id, false);
- } else {
- success.set(channel.id, true);
- await permissionOverwrites.edit(this.members.me!, permsForMe, { reason });
- await channel.send({
- embeds: [
- {
- author: { name: moderator.user.tag, icon_url: moderator.displayAvatarURL() },
- title: `This channel has been ${options.unlock ? 'un' : ''}locked`,
- description: options.reason ?? 'No reason provided',
- color: options.unlock ? colors.Green : colors.Red,
- timestamp: new Date().toISOString()
- }
- ]
- });
- }
- }
-
- if (errors.size) return errors;
- else return `success: ${success.filter((c) => c === true).size}`;
- })();
-
- this.client.emit(options.unlock ? 'bushUnlockdown' : 'bushLockdown', moderator, options.reason, success, options.all);
- return ret;
- }
-
- public override async quote(rawQuote: APIMessage, channel: GuildTextBasedChannel): Promise<Message | null> {
- if (!channel.isTextBased() || channel.isDMBased() || channel.guildId !== this.id || !this.members.me) return null;
- if (!channel.permissionsFor(this.members.me).has('ManageWebhooks')) return null;
-
- const quote = new Message(this.client, rawQuote);
-
- const target = channel instanceof ThreadChannel ? channel.parent : channel;
- if (!target) return null;
-
- const webhooks: Collection<string, Webhook> = await target.fetchWebhooks().catch((e) => e);
- if (!(webhooks instanceof Collection)) return null;
-
- // find a webhook that we can use
- let webhook = webhooks.find((w) => !!w.token) ?? null;
- if (!webhook)
- webhook = await target
- .createWebhook({
- name: `${this.client.user!.username} Quotes #${target.name}`,
- avatar: this.client.user!.displayAvatarURL({ size: 2048 }),
- reason: 'Creating a webhook for quoting'
- })
- .catch(() => null);
-
- if (!webhook) return null;
-
- const sendOptions: Omit<WebhookMessageOptions, 'flags'> = {};
-
- const displayName = quote.member?.displayName ?? quote.author.username;
-
- switch (quote.type) {
- case MessageType.Default:
- case MessageType.Reply:
- case MessageType.ChatInputCommand:
- case MessageType.ContextMenuCommand:
- case MessageType.ThreadStarterMessage:
- sendOptions.content = quote.content || undefined;
- sendOptions.threadId = channel instanceof ThreadChannel ? channel.id : undefined;
- sendOptions.embeds = quote.embeds.length ? quote.embeds : undefined;
- //@ts-expect-error: jank
- sendOptions.attachments = quote.attachments.size
- ? [...quote.attachments.values()].map((a) => AttachmentBuilder.from(a as JSONEncodable<AttachmentPayload>))
- : undefined;
-
- if (quote.stickers.size && !(quote.content || quote.embeds.length || quote.attachments.size))
- sendOptions.content = '[[This message has a sticker but not content]]';
-
- break;
- case MessageType.RecipientAdd: {
- const recipient = rawQuote.mentions[0];
- if (!recipient) {
- sendOptions.content = `${emojis.error} Cannot resolve recipient.`;
- break;
- }
-
- if (quote.channel.isThread()) {
- const recipientDisplay = quote.guild?.members.cache.get(recipient.id)?.displayName ?? recipient.username;
- sendOptions.content = `${emojis.join} ${displayName} added ${recipientDisplay} to the thread.`;
- } else {
- // this should never happen
- sendOptions.content = `${emojis.join} ${displayName} added ${recipient.username} to the group.`;
- }
-
- break;
- }
- case MessageType.RecipientRemove: {
- const recipient = rawQuote.mentions[0];
- if (!recipient) {
- sendOptions.content = `${emojis.error} Cannot resolve recipient.`;
- break;
- }
-
- if (quote.channel.isThread()) {
- const recipientDisplay = quote.guild?.members.cache.get(recipient.id)?.displayName ?? recipient.username;
- sendOptions.content = `${emojis.leave} ${displayName} removed ${recipientDisplay} from the thread.`;
- } else {
- // this should never happen
- sendOptions.content = `${emojis.leave} ${displayName} removed ${recipient.username} from the group.`;
- }
-
- break;
- }
-
- case MessageType.ChannelNameChange:
- sendOptions.content = `<:pencil:957988608994861118> ${displayName} changed the channel name: **${quote.content}**`;
-
- break;
-
- case MessageType.ChannelPinnedMessage:
- throw new Error('Not implemented yet: MessageType.ChannelPinnedMessage case');
- case MessageType.UserJoin: {
- const messages = [
- '{username} joined the party.',
- '{username} is here.',
- 'Welcome, {username}. We hope you brought pizza.',
- 'A wild {username} appeared.',
- '{username} just landed.',
- '{username} just slid into the server.',
- '{username} just showed up!',
- 'Welcome {username}. Say hi!',
- '{username} hopped into the server.',
- 'Everyone welcome {username}!',
- "Glad you're here, {username}.",
- 'Good to see you, {username}.',
- 'Yay you made it, {username}!'
- ];
-
- const timestamp = SnowflakeUtil.timestampFrom(quote.id);
-
- // this is the same way that the discord client decides what message to use.
- const message = messages[timestamp % messages.length].replace(/{username}/g, displayName);
-
- sendOptions.content = `${emojis.join} ${message}`;
- break;
- }
- case MessageType.GuildBoost:
- sendOptions.content = `<:NitroBoost:585558042309820447> ${displayName} just boosted the server${
- quote.content ? ` **${quote.content}** times` : ''
- }!`;
-
- break;
- case MessageType.GuildBoostTier1:
- case MessageType.GuildBoostTier2:
- case MessageType.GuildBoostTier3:
- sendOptions.content = `<:NitroBoost:585558042309820447> ${displayName} just boosted the server${
- quote.content ? ` **${quote.content}** times` : ''
- }! ${quote.guild?.name} has achieved **Level ${quote.type - 8}!**`;
-
- break;
- case MessageType.ChannelFollowAdd:
- sendOptions.content = `${displayName} has added **${quote.content}** to this channel. Its most important updates will show up here.`;
-
- break;
- case MessageType.GuildDiscoveryDisqualified:
- sendOptions.content =
- '<:SystemMessageCross:842172192418693173> This server has been removed from Server Discovery because it no longer passes all the requirements. Check Server Settings for more details.';
-
- break;
- case MessageType.GuildDiscoveryRequalified:
- sendOptions.content =
- '<:SystemMessageCheck:842172191801212949> This server is eligible for Server Discovery again and has been automatically relisted!';
-
- break;
- case MessageType.GuildDiscoveryGracePeriodInitialWarning:
- sendOptions.content =
- '<:SystemMessageWarn:842172192401915971> This server has failed Discovery activity requirements for 1 week. If this server fails for 4 weeks in a row, it will be automatically removed from Discovery.';
-
- break;
- case MessageType.GuildDiscoveryGracePeriodFinalWarning:
- sendOptions.content =
- '<:SystemMessageWarn:842172192401915971> This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery.';
-
- break;
- case MessageType.ThreadCreated: {
- const threadId = rawQuote.message_reference?.channel_id;
-
- sendOptions.content = `<:thread:865033845753249813> ${displayName} started a thread: **[${quote.content}](https://discord.com/channels/${quote.guildId}/${threadId}
- )**. See all threads.`;
-
- break;
- }
- case MessageType.GuildInviteReminder:
- sendOptions.content = 'Wondering who to invite? Start by inviting anyone who can help you build the server!';
-
- break;
- // todo: use enum for this
- case 24 as MessageType: {
- const embed = quote.embeds[0];
- // eslint-disable-next-line deprecation/deprecation
- assert.equal(embed.data.type, 'auto_moderation_message');
- const ruleName = embed.fields!.find((f) => f.name === 'rule_name')!.value;
- const channelId = embed.fields!.find((f) => f.name === 'channel_id')!.value;
- const keyword = embed.fields!.find((f) => f.name === 'keyword')!.value;
-
- sendOptions.username = `AutoMod (${quote.member?.displayName ?? quote.author.username})`;
- sendOptions.content = `Automod has blocked a message in <#${channelId}>`;
- sendOptions.embeds = [
- {
- title: quote.member?.displayName ?? quote.author.username,
- description: embed.description ?? 'There is no content???',
- footer: {
- text: `Keyword: ${keyword} • Rule: ${ruleName}`
- },
- color: 0x36393f
- }
- ];
-
- break;
- }
- case MessageType.ChannelIconChange:
- case MessageType.Call:
- default:
- sendOptions.content = `${emojis.error} I cannot quote messages of type **${
- MessageType[quote.type] || quote.type
- }** messages, please report this to my developers.`;
-
- break;
- }
-
- sendOptions.allowedMentions = AllowedMentions.none();
- sendOptions.username ??= quote.member?.displayName ?? quote.author.username;
- sendOptions.avatarURL = quote.member?.displayAvatarURL({ size: 2048 }) ?? quote.author.displayAvatarURL({ size: 2048 });
-
- return await webhook.send(sendOptions); /* .catch((e: any) => e); */
- }
-}
-
-/**
- * Options for unbanning a user
- */
-export interface GuildBushUnbanOptions {
- /**
- * The user to unban
- */
- user: UserResolvable | User;
-
- /**
- * The reason for unbanning the user
- */
- reason?: string | null;
-
- /**
- * The moderator who unbanned the user
- */
- moderator?: UserResolvable;
-
- /**
- * The evidence for the unban
- */
- evidence?: string;
-}
-
-export interface GuildMassBanOneOptions {
- /**
- * The user to ban
- */
- user: Snowflake;
-
- /**
- * The reason to ban the user
- */
- reason: string;
-
- /**
- * The moderator who banned the user
- */
- moderator: Snowflake;
-
- /**
- * The number of days to delete the user's messages for
- */
- deleteDays?: number;
-}
-
-/**
- * Options for banning a user
- */
-export interface GuildBushBanOptions {
- /**
- * The user to ban
- */
- user: UserResolvable;
-
- /**
- * The reason to ban the user
- */
- reason?: string | null;
-
- /**
- * The moderator who banned the user
- */
- moderator?: UserResolvable;
-
- /**
- * The duration of the ban
- */
- duration?: number;
-
- /**
- * The number of days to delete the user's messages for
- */
- deleteDays?: number;
-
- /**
- * The evidence for the ban
- */
- evidence?: string;
-}
-
-type ValueOf<T> = T[keyof T];
-
-export const unbanResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse,
- ...punishmentEntryRemove,
- NOT_BANNED: 'user not banned'
-} as const);
-
-/**
- * Response returned when unbanning a user
- */
-export type UnbanResponse = ValueOf<typeof unbanResponse>;
-
-/**
- * Options for locking down channel(s)
- */
-export interface LockdownOptions {
- /**
- * The moderator responsible for the lockdown
- */
- moderator: GuildMemberResolvable;
-
- /**
- * Whether to lock down all (specified) channels
- */
- all: boolean;
-
- /**
- * Reason for the lockdown
- */
- reason?: string;
-
- /**
- * A specific channel to lockdown
- */
- channel?: ThreadChannel | NewsChannel | TextChannel | VoiceChannel;
-
- /**
- * Whether or not to unlock the channel(s) instead of locking them
- */
- unlock?: boolean;
-}
-
-/**
- * Response returned when locking down a channel
- */
-export type LockdownResponse =
- | `success: ${number}`
- | 'all not chosen and no channel specified'
- | 'no channels configured'
- | `invalid channel configured: ${string}`
- | 'moderator not found'
- | Collection<string, Error>;
diff --git a/src/lib/extensions/discord.js/ExtendedGuildMember.ts b/src/lib/extensions/discord.js/ExtendedGuildMember.ts
deleted file mode 100644
index f8add83..0000000
--- a/src/lib/extensions/discord.js/ExtendedGuildMember.ts
+++ /dev/null
@@ -1,1255 +0,0 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
-import { formatError, Moderation, ModLogType, Time, type BushClientEvents, type PunishmentTypeDM, type ValueOf } from '#lib';
-import {
- ChannelType,
- GuildMember,
- PermissionFlagsBits,
- type GuildChannelResolvable,
- type GuildTextBasedChannel,
- type Role
-} from 'discord.js';
-/* eslint-enable @typescript-eslint/no-unused-vars */
-
-declare module 'discord.js' {
- export interface GuildMember {
- /**
- * Send a punishment dm to the user.
- * @param punishment The punishment that the user has received.
- * @param reason The reason for the user's punishment.
- * @param duration The duration of the punishment.
- * @param modlog The modlog case id so the user can make an appeal.
- * @param sendFooter Whether or not to send the guild's punishment footer with the dm.
- * @returns Whether or not the dm was sent successfully.
- */
- bushPunishDM(
- punishment: PunishmentTypeDM,
- reason?: string | null,
- duration?: number,
- modlog?: string,
- sendFooter?: boolean
- ): Promise<boolean>;
- /**
- * Warn the user, create a modlog entry, and send a dm to the user.
- * @param options Options for warning the user.
- * @returns An object with the result of the warning, and the case number of the warn.
- * @emits {@link BushClientEvents.bushWarn}
- */
- bushWarn(options: BushPunishmentOptions): Promise<{ result: WarnResponse; caseNum: number | null }>;
- /**
- * Add a role to the user, if it is a punishment create a modlog entry, and create a punishment entry if it is temporary or a punishment.
- * @param options Options for adding a role to the user.
- * @returns A status message for adding the add.
- * @emits {@link BushClientEvents.bushPunishRole}
- */
- bushAddRole(options: AddRoleOptions): Promise<AddRoleResponse>;
- /**
- * Remove a role from the user, if it is a punishment create a modlog entry, and destroy a punishment entry if it was temporary or a punishment.
- * @param options Options for removing a role from the user.
- * @returns A status message for removing the role.
- * @emits {@link BushClientEvents.bushPunishRoleRemove}
- */
- bushRemoveRole(options: RemoveRoleOptions): Promise<RemoveRoleResponse>;
- /**
- * Mute the user, create a modlog entry, creates a punishment entry, and dms the user.
- * @param options Options for muting the user.
- * @returns A status message for muting the user.
- * @emits {@link BushClientEvents.bushMute}
- */
- bushMute(options: BushTimedPunishmentOptions): Promise<MuteResponse>;
- /**
- * Unmute the user, create a modlog entry, remove the punishment entry, and dm the user.
- * @param options Options for unmuting the user.
- * @returns A status message for unmuting the user.
- * @emits {@link BushClientEvents.bushUnmute}
- */
- bushUnmute(options: BushPunishmentOptions): Promise<UnmuteResponse>;
- /**
- * Kick the user, create a modlog entry, and dm the user.
- * @param options Options for kicking the user.
- * @returns A status message for kicking the user.
- * @emits {@link BushClientEvents.bushKick}
- */
- bushKick(options: BushPunishmentOptions): Promise<KickResponse>;
- /**
- * Ban the user, create a modlog entry, create a punishment entry, and dm the user.
- * @param options Options for banning the user.
- * @returns A status message for banning the user.
- * @emits {@link BushClientEvents.bushBan}
- */
- bushBan(options: BushBanOptions): Promise<Exclude<BanResponse, typeof banResponse['ALREADY_BANNED']>>;
- /**
- * Prevents a user from speaking in a channel.
- * @param options Options for blocking the user.
- */
- bushBlock(options: BlockOptions): Promise<BlockResponse>;
- /**
- * Allows a user to speak in a channel.
- * @param options Options for unblocking the user.
- */
- bushUnblock(options: UnblockOptions): Promise<UnblockResponse>;
- /**
- * Mutes a user using discord's timeout feature.
- * @param options Options for timing out the user.
- */
- bushTimeout(options: BushTimeoutOptions): Promise<TimeoutResponse>;
- /**
- * Removes a timeout from a user.
- * @param options Options for removing the timeout.
- */
- bushRemoveTimeout(options: BushPunishmentOptions): Promise<RemoveTimeoutResponse>;
- /**
- * Whether or not the user is an owner of the bot.
- */
- isOwner(): boolean;
- /**
- * Whether or not the user is a super user of the bot.
- */
- isSuperUser(): boolean;
- }
-}
-
-/**
- * Represents a member of a guild on Discord.
- */
-export class ExtendedGuildMember extends GuildMember {
- /**
- * Send a punishment dm to the user.
- * @param punishment The punishment that the user has received.
- * @param reason The reason for the user's punishment.
- * @param duration The duration of the punishment.
- * @param modlog The modlog case id so the user can make an appeal.
- * @param sendFooter Whether or not to send the guild's punishment footer with the dm.
- * @returns Whether or not the dm was sent successfully.
- */
- public override async bushPunishDM(
- punishment: PunishmentTypeDM,
- reason?: string | null,
- duration?: number,
- modlog?: string,
- sendFooter = true
- ): Promise<boolean> {
- return Moderation.punishDM({
- client: this.client,
- modlog,
- guild: this.guild,
- user: this,
- punishment,
- reason: reason ?? undefined,
- duration,
- sendFooter
- });
- }
-
- /**
- * Warn the user, create a modlog entry, and send a dm to the user.
- * @param options Options for warning the user.
- * @returns An object with the result of the warning, and the case number of the warn.
- * @emits {@link BushClientEvents.bushWarn}
- */
- public override async bushWarn(options: BushPunishmentOptions): Promise<{ result: WarnResponse; caseNum: number | null }> {
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return { result: warnResponse.CANNOT_RESOLVE_USER, caseNum: null };
-
- const ret = await (async (): Promise<{ result: WarnResponse; caseNum: number | null }> => {
- // add modlog entry
- const result = await Moderation.createModLogEntry(
- {
- client: this.client,
- type: ModLogType.WARN,
- user: this,
- moderator: moderator.id,
- reason: options.reason,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- },
- true
- );
- caseID = result.log?.id;
- if (!result || !result.log) return { result: warnResponse.MODLOG_ERROR, caseNum: null };
-
- if (!options.silent) {
- // dm user
- const dmSuccess = await this.bushPunishDM('warned', options.reason);
- dmSuccessEvent = dmSuccess;
- if (!dmSuccess) return { result: warnResponse.DM_ERROR, caseNum: result.caseNum };
- }
-
- return { result: warnResponse.SUCCESS, caseNum: result.caseNum };
- })();
- if (!([warnResponse.MODLOG_ERROR] as const).includes(ret.result) && !options.silent)
- this.client.emit('bushWarn', this, moderator, this.guild, options.reason ?? undefined, caseID!, dmSuccessEvent!);
- return ret;
- }
-
- /**
- * Add a role to the user, if it is a punishment create a modlog entry, and create a punishment entry if it is temporary or a punishment.
- * @param options Options for adding a role to the user.
- * @returns A status message for adding the add.
- * @emits {@link BushClientEvents.bushPunishRole}
- */
- public override async bushAddRole(options: AddRoleOptions): Promise<AddRoleResponse> {
- // checks
- if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return addRoleResponse.MISSING_PERMISSIONS;
- const ifShouldAddRole = this.#checkIfShouldAddRole(options.role, options.moderator);
- if (ifShouldAddRole !== true) return ifShouldAddRole;
-
- let caseID: string | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return addRoleResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- if (options.addToModlog || options.duration) {
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: options.duration ? ModLogType.TEMP_PUNISHMENT_ROLE : ModLogType.PERM_PUNISHMENT_ROLE,
- guild: this.guild,
- moderator: moderator.id,
- user: this,
- reason: 'N/A',
- pseudo: !options.addToModlog,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
-
- if (!modlog) return addRoleResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- if (options.addToModlog || options.duration) {
- const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
- client: this.client,
- type: 'role',
- user: this,
- guild: this.guild,
- modlog: modlog.id,
- duration: options.duration,
- extraInfo: options.role.id
- });
- if (!punishmentEntrySuccess) return addRoleResponse.PUNISHMENT_ENTRY_ADD_ERROR;
- }
- }
-
- const removeRoleSuccess = await this.roles.add(options.role, `${moderator.tag}`);
- if (!removeRoleSuccess) return addRoleResponse.ACTION_ERROR;
-
- return addRoleResponse.SUCCESS;
- })();
- if (
- !(
- [addRoleResponse.ACTION_ERROR, addRoleResponse.MODLOG_ERROR, addRoleResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const
- ).includes(ret) &&
- options.addToModlog &&
- !options.silent
- )
- this.client.emit(
- 'bushPunishRole',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- options.duration ?? 0,
- options.role,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Remove a role from the user, if it is a punishment create a modlog entry, and destroy a punishment entry if it was temporary or a punishment.
- * @param options Options for removing a role from the user.
- * @returns A status message for removing the role.
- * @emits {@link BushClientEvents.bushPunishRoleRemove}
- */
- public override async bushRemoveRole(options: RemoveRoleOptions): Promise<RemoveRoleResponse> {
- // checks
- if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return removeRoleResponse.MISSING_PERMISSIONS;
- const ifShouldAddRole = this.#checkIfShouldAddRole(options.role, options.moderator);
- if (ifShouldAddRole !== true) return ifShouldAddRole;
-
- let caseID: string | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return removeRoleResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- if (options.addToModlog) {
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: ModLogType.REMOVE_PUNISHMENT_ROLE,
- guild: this.guild,
- moderator: moderator.id,
- user: this,
- reason: 'N/A',
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
-
- if (!modlog) return removeRoleResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- const punishmentEntrySuccess = await Moderation.removePunishmentEntry({
- client: this.client,
- type: 'role',
- user: this,
- guild: this.guild,
- extraInfo: options.role.id
- });
-
- if (!punishmentEntrySuccess) return removeRoleResponse.PUNISHMENT_ENTRY_REMOVE_ERROR;
- }
-
- const removeRoleSuccess = await this.roles.remove(options.role, `${moderator.tag}`);
- if (!removeRoleSuccess) return removeRoleResponse.ACTION_ERROR;
-
- return removeRoleResponse.SUCCESS;
- })();
-
- if (
- !(
- [
- removeRoleResponse.ACTION_ERROR,
- removeRoleResponse.MODLOG_ERROR,
- removeRoleResponse.PUNISHMENT_ENTRY_REMOVE_ERROR
- ] as const
- ).includes(ret) &&
- options.addToModlog &&
- !options.silent
- )
- this.client.emit(
- 'bushPunishRoleRemove',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- options.role,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Check whether or not a role should be added/removed from the user based on hierarchy.
- * @param role The role to check if can be modified.
- * @param moderator The moderator that is trying to add/remove the role.
- * @returns `true` if the role should be added/removed or a string for the reason why it shouldn't.
- */
- #checkIfShouldAddRole(
- role: Role | Role,
- moderator?: GuildMember
- ): true | 'user hierarchy' | 'role managed' | 'client hierarchy' {
- if (moderator && moderator.roles.highest.position <= role.position && this.guild.ownerId !== this.user.id) {
- return shouldAddRoleResponse.USER_HIERARCHY;
- } else if (role.managed) {
- return shouldAddRoleResponse.ROLE_MANAGED;
- } else if (this.guild.members.me!.roles.highest.position <= role.position) {
- return shouldAddRoleResponse.CLIENT_HIERARCHY;
- }
- return true;
- }
-
- /**
- * Mute the user, create a modlog entry, creates a punishment entry, and dms the user.
- * @param options Options for muting the user.
- * @returns A status message for muting the user.
- * @emits {@link BushClientEvents.bushMute}
- */
- public override async bushMute(options: BushTimedPunishmentOptions): Promise<MuteResponse> {
- // checks
- const checks = await Moderation.checkMutePermissions(this.guild);
- if (checks !== true) return checks;
-
- const muteRoleID = (await this.guild.getSetting('muteRole'))!;
- const muteRole = this.guild.roles.cache.get(muteRoleID)!;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return muteResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- // add role
- const muteSuccess = await this.roles
- .add(muteRole, `[Mute] ${moderator.tag} | ${options.reason ?? 'No reason provided.'}`)
- .catch(async (e) => {
- await this.client.console.warn('muteRoleAddError', e);
- this.client.console.debug(e);
- return false;
- });
- if (!muteSuccess) return muteResponse.ACTION_ERROR;
-
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: options.duration ? ModLogType.TEMP_MUTE : ModLogType.PERM_MUTE,
- user: this,
- moderator: moderator.id,
- reason: options.reason,
- duration: options.duration,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
-
- if (!modlog) return muteResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- // add punishment entry so they can be unmuted later
- const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
- client: this.client,
- type: 'mute',
- user: this,
- guild: this.guild,
- duration: options.duration,
- modlog: modlog.id
- });
-
- if (!punishmentEntrySuccess) return muteResponse.PUNISHMENT_ENTRY_ADD_ERROR;
-
- if (!options.silent) {
- // dm user
- const dmSuccess = await this.bushPunishDM('muted', options.reason, options.duration ?? 0, modlog.id);
- dmSuccessEvent = dmSuccess;
- if (!dmSuccess) return muteResponse.DM_ERROR;
- }
-
- return muteResponse.SUCCESS;
- })();
-
- if (
- !([muteResponse.ACTION_ERROR, muteResponse.MODLOG_ERROR, muteResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const).includes(ret) &&
- !options.silent
- )
- this.client.emit(
- 'bushMute',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- options.duration ?? 0,
- dmSuccessEvent!,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Unmute the user, create a modlog entry, remove the punishment entry, and dm the user.
- * @param options Options for unmuting the user.
- * @returns A status message for unmuting the user.
- * @emits {@link BushClientEvents.bushUnmute}
- */
- public override async bushUnmute(options: BushPunishmentOptions): Promise<UnmuteResponse> {
- // checks
- const checks = await Moderation.checkMutePermissions(this.guild);
- if (checks !== true) return checks;
-
- const muteRoleID = (await this.guild.getSetting('muteRole'))!;
- const muteRole = this.guild.roles.cache.get(muteRoleID)!;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return unmuteResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- // remove role
- const muteSuccess = await this.roles
- .remove(muteRole, `[Unmute] ${moderator.tag} | ${options.reason ?? 'No reason provided.'}`)
- .catch(async (e) => {
- await this.client.console.warn('muteRoleAddError', formatError(e, true));
- return false;
- });
- if (!muteSuccess) return unmuteResponse.ACTION_ERROR;
-
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: ModLogType.UNMUTE,
- user: this,
- moderator: moderator.id,
- reason: options.reason,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
-
- if (!modlog) return unmuteResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- // remove mute entry
- const removePunishmentEntrySuccess = await Moderation.removePunishmentEntry({
- client: this.client,
- type: 'mute',
- user: this,
- guild: this.guild
- });
-
- if (!removePunishmentEntrySuccess) return unmuteResponse.PUNISHMENT_ENTRY_REMOVE_ERROR;
-
- if (!options.silent) {
- // dm user
- const dmSuccess = await this.bushPunishDM('unmuted', options.reason, undefined, '', false);
- dmSuccessEvent = dmSuccess;
- if (!dmSuccess) return unmuteResponse.DM_ERROR;
- }
-
- return unmuteResponse.SUCCESS;
- })();
-
- if (
- !(
- [unmuteResponse.ACTION_ERROR, unmuteResponse.MODLOG_ERROR, unmuteResponse.PUNISHMENT_ENTRY_REMOVE_ERROR] as const
- ).includes(ret) &&
- !options.silent
- )
- this.client.emit(
- 'bushUnmute',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- dmSuccessEvent!,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Kick the user, create a modlog entry, and dm the user.
- * @param options Options for kicking the user.
- * @returns A status message for kicking the user.
- * @emits {@link BushClientEvents.bushKick}
- */
- public override async bushKick(options: BushPunishmentOptions): Promise<KickResponse> {
- // checks
- if (!this.guild.members.me?.permissions.has(PermissionFlagsBits.KickMembers) || !this.kickable)
- return kickResponse.MISSING_PERMISSIONS;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return kickResponse.CANNOT_RESOLVE_USER;
- const ret = await (async () => {
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: ModLogType.KICK,
- user: this,
- moderator: moderator.id,
- reason: options.reason,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
- if (!modlog) return kickResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- // dm user
- const dmSuccess = options.silent ? null : await this.bushPunishDM('kicked', options.reason, undefined, modlog.id);
- dmSuccessEvent = dmSuccess ?? undefined;
-
- // kick
- const kickSuccess = await this.kick(`${moderator?.tag} | ${options.reason ?? 'No reason provided.'}`).catch(() => false);
- if (!kickSuccess) return kickResponse.ACTION_ERROR;
-
- if (dmSuccess === false) return kickResponse.DM_ERROR;
- return kickResponse.SUCCESS;
- })();
- if (!([kickResponse.ACTION_ERROR, kickResponse.MODLOG_ERROR] as const).includes(ret) && !options.silent)
- this.client.emit(
- 'bushKick',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- dmSuccessEvent!,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Ban the user, create a modlog entry, create a punishment entry, and dm the user.
- * @param options Options for banning the user.
- * @returns A status message for banning the user.
- * @emits {@link BushClientEvents.bushBan}
- */
- public override async bushBan(options: BushBanOptions): Promise<Exclude<BanResponse, typeof banResponse['ALREADY_BANNED']>> {
- // checks
- if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.BanMembers) || !this.bannable)
- return banResponse.MISSING_PERMISSIONS;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return banResponse.CANNOT_RESOLVE_USER;
-
- // ignore result, they should still be banned even if their mute cannot be removed
- await this.bushUnmute({
- reason: 'User is about to be banned, a mute is no longer necessary.',
- moderator: this.guild.members.me!,
- silent: true
- });
-
- const ret = await (async () => {
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: options.duration ? ModLogType.TEMP_BAN : ModLogType.PERM_BAN,
- user: this,
- moderator: moderator.id,
- reason: options.reason,
- duration: options.duration,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
- if (!modlog) return banResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- // dm user
- const dmSuccess = options.silent
- ? null
- : await this.bushPunishDM('banned', options.reason, options.duration ?? 0, modlog.id);
- dmSuccessEvent = dmSuccess ?? undefined;
-
- // ban
- const banSuccess = await this.ban({
- reason: `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`,
- deleteMessageDays: options.deleteDays
- }).catch(() => false);
- if (!banSuccess) return banResponse.ACTION_ERROR;
-
- // add punishment entry so they can be unbanned later
- const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
- client: this.client,
- type: 'ban',
- user: this,
- guild: this.guild,
- duration: options.duration,
- modlog: modlog.id
- });
- if (!punishmentEntrySuccess) return banResponse.PUNISHMENT_ENTRY_ADD_ERROR;
-
- if (!dmSuccess) return banResponse.DM_ERROR;
- return banResponse.SUCCESS;
- })();
- if (
- !([banResponse.ACTION_ERROR, banResponse.MODLOG_ERROR, banResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const).includes(ret) &&
- !options.silent
- )
- this.client.emit(
- 'bushBan',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- options.duration ?? 0,
- dmSuccessEvent!,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Prevents a user from speaking in a channel.
- * @param options Options for blocking the user.
- */
- public override async bushBlock(options: BlockOptions): Promise<BlockResponse> {
- const channel = this.guild.channels.resolve(options.channel);
- if (!channel || (!channel.isTextBased() && !channel.isThread())) return blockResponse.INVALID_CHANNEL;
-
- // checks
- if (!channel.permissionsFor(this.guild.members.me!)!.has(PermissionFlagsBits.ManageChannels))
- return blockResponse.MISSING_PERMISSIONS;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return blockResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- // change channel permissions
- const channelToUse = channel.isThread() ? channel.parent! : channel;
- const perm = channel.isThread() ? { SendMessagesInThreads: false } : { SendMessages: false };
- const blockSuccess = await channelToUse.permissionOverwrites
- .edit(this, perm, { reason: `[Block] ${moderator.tag} | ${options.reason ?? 'No reason provided.'}` })
- .catch(() => false);
- if (!blockSuccess) return blockResponse.ACTION_ERROR;
-
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: options.duration ? ModLogType.TEMP_CHANNEL_BLOCK : ModLogType.PERM_CHANNEL_BLOCK,
- user: this,
- moderator: moderator.id,
- reason: options.reason,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
- if (!modlog) return blockResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- // add punishment entry so they can be unblocked later
- const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
- client: this.client,
- type: 'block',
- user: this,
- guild: this.guild,
- duration: options.duration,
- modlog: modlog.id,
- extraInfo: channel.id
- });
- if (!punishmentEntrySuccess) return blockResponse.PUNISHMENT_ENTRY_ADD_ERROR;
-
- // dm user
- const dmSuccess = options.silent
- ? null
- : await Moderation.punishDM({
- client: this.client,
- punishment: 'blocked',
- reason: options.reason ?? undefined,
- duration: options.duration ?? 0,
- modlog: modlog.id,
- guild: this.guild,
- user: this,
- sendFooter: true,
- channel: channel.id
- });
- dmSuccessEvent = !!dmSuccess;
- if (!dmSuccess) return blockResponse.DM_ERROR;
-
- return blockResponse.SUCCESS;
- })();
-
- if (
- !([blockResponse.ACTION_ERROR, blockResponse.MODLOG_ERROR, blockResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const).includes(
- ret
- ) &&
- !options.silent
- )
- this.client.emit(
- 'bushBlock',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- options.duration ?? 0,
- dmSuccessEvent!,
- channel,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Allows a user to speak in a channel.
- * @param options Options for unblocking the user.
- */
- public override async bushUnblock(options: UnblockOptions): Promise<UnblockResponse> {
- const _channel = this.guild.channels.resolve(options.channel);
- if (!_channel || (_channel.type !== ChannelType.GuildText && !_channel.isThread())) return unblockResponse.INVALID_CHANNEL;
- const channel = _channel as GuildTextBasedChannel;
-
- // checks
- if (!channel.permissionsFor(this.guild.members.me!)!.has(PermissionFlagsBits.ManageChannels))
- return unblockResponse.MISSING_PERMISSIONS;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return unblockResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- // change channel permissions
- const channelToUse = channel.isThread() ? channel.parent! : channel;
- const perm = channel.isThread() ? { SendMessagesInThreads: null } : { SendMessages: null };
- const blockSuccess = await channelToUse.permissionOverwrites
- .edit(this, perm, { reason: `[Unblock] ${moderator.tag} | ${options.reason ?? 'No reason provided.'}` })
- .catch(() => false);
- if (!blockSuccess) return unblockResponse.ACTION_ERROR;
-
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: ModLogType.CHANNEL_UNBLOCK,
- user: this,
- moderator: moderator.id,
- reason: options.reason,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
- if (!modlog) return unblockResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- // remove punishment entry
- const punishmentEntrySuccess = await Moderation.removePunishmentEntry({
- client: this.client,
- type: 'block',
- user: this,
- guild: this.guild,
- extraInfo: channel.id
- });
- if (!punishmentEntrySuccess) return unblockResponse.ACTION_ERROR;
-
- // dm user
- const dmSuccess = options.silent
- ? null
- : await Moderation.punishDM({
- client: this.client,
- punishment: 'unblocked',
- reason: options.reason ?? undefined,
- guild: this.guild,
- user: this,
- sendFooter: false,
- channel: channel.id
- });
- dmSuccessEvent = !!dmSuccess;
- if (!dmSuccess) return blockResponse.DM_ERROR;
-
- dmSuccessEvent = !!dmSuccess;
- if (!dmSuccess) return unblockResponse.DM_ERROR;
-
- return unblockResponse.SUCCESS;
- })();
-
- if (
- !([unblockResponse.ACTION_ERROR, unblockResponse.MODLOG_ERROR, unblockResponse.ACTION_ERROR] as const).includes(ret) &&
- !options.silent
- )
- this.client.emit(
- 'bushUnblock',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- dmSuccessEvent!,
- channel,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Mutes a user using discord's timeout feature.
- * @param options Options for timing out the user.
- */
- public override async bushTimeout(options: BushTimeoutOptions): Promise<TimeoutResponse> {
- // checks
- if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ModerateMembers)) return timeoutResponse.MISSING_PERMISSIONS;
-
- const twentyEightDays = Time.Day * 28;
- if (options.duration > twentyEightDays) return timeoutResponse.INVALID_DURATION;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return timeoutResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- // timeout
- const timeoutSuccess = await this.timeout(
- options.duration,
- `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`
- ).catch(() => false);
- if (!timeoutSuccess) return timeoutResponse.ACTION_ERROR;
-
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: ModLogType.TIMEOUT,
- user: this,
- moderator: moderator.id,
- reason: options.reason,
- duration: options.duration,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
-
- if (!modlog) return timeoutResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- if (!options.silent) {
- // dm user
- const dmSuccess = await this.bushPunishDM('timedout', options.reason, options.duration, modlog.id);
- dmSuccessEvent = dmSuccess;
- if (!dmSuccess) return timeoutResponse.DM_ERROR;
- }
-
- return timeoutResponse.SUCCESS;
- })();
-
- if (!([timeoutResponse.ACTION_ERROR, timeoutResponse.MODLOG_ERROR] as const).includes(ret) && !options.silent)
- this.client.emit(
- 'bushTimeout',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- options.duration ?? 0,
- dmSuccessEvent!,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Removes a timeout from a user.
- * @param options Options for removing the timeout.
- */
- public override async bushRemoveTimeout(options: BushPunishmentOptions): Promise<RemoveTimeoutResponse> {
- // checks
- if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ModerateMembers))
- return removeTimeoutResponse.MISSING_PERMISSIONS;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return removeTimeoutResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- // remove timeout
- const timeoutSuccess = await this.timeout(null, `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`).catch(
- () => false
- );
- if (!timeoutSuccess) return removeTimeoutResponse.ACTION_ERROR;
-
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: ModLogType.REMOVE_TIMEOUT,
- user: this,
- moderator: moderator.id,
- reason: options.reason,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
-
- if (!modlog) return removeTimeoutResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- if (!options.silent) {
- // dm user
- const dmSuccess = await this.bushPunishDM('untimedout', options.reason, undefined, '', false);
- dmSuccessEvent = dmSuccess;
- if (!dmSuccess) return removeTimeoutResponse.DM_ERROR;
- }
-
- return removeTimeoutResponse.SUCCESS;
- })();
-
- if (!([removeTimeoutResponse.ACTION_ERROR, removeTimeoutResponse.MODLOG_ERROR] as const).includes(ret) && !options.silent)
- this.client.emit(
- 'bushRemoveTimeout',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- dmSuccessEvent!,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Whether or not the user is an owner of the bot.
- */
- public override isOwner(): boolean {
- return this.client.isOwner(this);
- }
-
- /**
- * Whether or not the user is a super user of the bot.
- */
- public override isSuperUser(): boolean {
- return this.client.isSuperUser(this);
- }
-}
-
-/**
- * Options for punishing a user.
- */
-export interface BushPunishmentOptions {
- /**
- * The reason for the punishment.
- */
- reason?: string | null;
-
- /**
- * The moderator who punished the user.
- */
- moderator?: GuildMember;
-
- /**
- * Evidence for the punishment.
- */
- evidence?: string;
-
- /**
- * Makes the punishment silent by not sending the user a punishment dm and not broadcasting the event to be logged.
- */
- silent?: boolean;
-}
-
-/**
- * Punishment options for punishments that can be temporary.
- */
-export interface BushTimedPunishmentOptions extends BushPunishmentOptions {
- /**
- * The duration of the punishment.
- */
- duration?: number;
-}
-
-/**
- * Options for a role add punishment.
- */
-export interface AddRoleOptions extends BushTimedPunishmentOptions {
- /**
- * The role to add to the user.
- */
- role: Role;
-
- /**
- * Whether to create a modlog entry for this punishment.
- */
- addToModlog: boolean;
-}
-
-/**
- * Options for a role remove punishment.
- */
-export interface RemoveRoleOptions extends BushTimedPunishmentOptions {
- /**
- * The role to remove from the user.
- */
- role: Role;
-
- /**
- * Whether to create a modlog entry for this punishment.
- */
- addToModlog: boolean;
-}
-
-/**
- * Options for banning a user.
- */
-export interface BushBanOptions extends BushTimedPunishmentOptions {
- /**
- * The number of days to delete the user's messages for.
- */
- deleteDays?: number;
-}
-
-/**
- * Options for blocking a user from a channel.
- */
-export interface BlockOptions extends BushTimedPunishmentOptions {
- /**
- * The channel to block the user from.
- */
- channel: GuildChannelResolvable;
-}
-
-/**
- * Options for unblocking a user from a channel.
- */
-export interface UnblockOptions extends BushPunishmentOptions {
- /**
- * The channel to unblock the user from.
- */
- channel: GuildChannelResolvable;
-}
-
-/**
- * Punishment options for punishments that can be temporary.
- */
-export interface BushTimeoutOptions extends BushPunishmentOptions {
- /**
- * The duration of the punishment.
- */
- duration: number;
-}
-
-export const basePunishmentResponse = Object.freeze({
- SUCCESS: 'success',
- MODLOG_ERROR: 'error creating modlog entry',
- ACTION_ERROR: 'error performing action',
- CANNOT_RESOLVE_USER: 'cannot resolve user'
-} as const);
-
-export const dmResponse = Object.freeze({
- ...basePunishmentResponse,
- DM_ERROR: 'failed to dm'
-} as const);
-
-export const permissionsResponse = Object.freeze({
- MISSING_PERMISSIONS: 'missing permissions'
-} as const);
-
-export const punishmentEntryAdd = Object.freeze({
- PUNISHMENT_ENTRY_ADD_ERROR: 'error creating punishment entry'
-} as const);
-
-export const punishmentEntryRemove = Object.freeze({
- PUNISHMENT_ENTRY_REMOVE_ERROR: 'error removing punishment entry'
-} as const);
-
-export const shouldAddRoleResponse = Object.freeze({
- USER_HIERARCHY: 'user hierarchy',
- CLIENT_HIERARCHY: 'client hierarchy',
- ROLE_MANAGED: 'role managed'
-} as const);
-
-export const baseBlockResponse = Object.freeze({
- INVALID_CHANNEL: 'invalid channel'
-} as const);
-
-export const baseMuteResponse = Object.freeze({
- NO_MUTE_ROLE: 'no mute role',
- MUTE_ROLE_INVALID: 'invalid mute role',
- MUTE_ROLE_NOT_MANAGEABLE: 'mute role not manageable'
-} as const);
-
-export const warnResponse = Object.freeze({
- ...dmResponse
-} as const);
-
-export const addRoleResponse = Object.freeze({
- ...basePunishmentResponse,
- ...permissionsResponse,
- ...shouldAddRoleResponse,
- ...punishmentEntryAdd
-} as const);
-
-export const removeRoleResponse = Object.freeze({
- ...basePunishmentResponse,
- ...permissionsResponse,
- ...shouldAddRoleResponse,
- ...punishmentEntryRemove
-} as const);
-
-export const muteResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse,
- ...baseMuteResponse,
- ...punishmentEntryAdd
-} as const);
-
-export const unmuteResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse,
- ...baseMuteResponse,
- ...punishmentEntryRemove
-} as const);
-
-export const kickResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse
-} as const);
-
-export const banResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse,
- ...punishmentEntryAdd,
- ALREADY_BANNED: 'already banned'
-} as const);
-
-export const blockResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse,
- ...baseBlockResponse,
- ...punishmentEntryAdd
-});
-
-export const unblockResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse,
- ...baseBlockResponse,
- ...punishmentEntryRemove
-});
-
-export const timeoutResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse,
- INVALID_DURATION: 'duration too long'
-} as const);
-
-export const removeTimeoutResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse
-} as const);
-
-/**
- * Response returned when warning a user.
- */
-export type WarnResponse = ValueOf<typeof warnResponse>;
-
-/**
- * Response returned when adding a role to a user.
- */
-export type AddRoleResponse = ValueOf<typeof addRoleResponse>;
-
-/**
- * Response returned when removing a role from a user.
- */
-export type RemoveRoleResponse = ValueOf<typeof removeRoleResponse>;
-
-/**
- * Response returned when muting a user.
- */
-export type MuteResponse = ValueOf<typeof muteResponse>;
-
-/**
- * Response returned when unmuting a user.
- */
-export type UnmuteResponse = ValueOf<typeof unmuteResponse>;
-
-/**
- * Response returned when kicking a user.
- */
-export type KickResponse = ValueOf<typeof kickResponse>;
-
-/**
- * Response returned when banning a user.
- */
-export type BanResponse = ValueOf<typeof banResponse>;
-
-/**
- * Response returned when blocking a user.
- */
-export type BlockResponse = ValueOf<typeof blockResponse>;
-
-/**
- * Response returned when unblocking a user.
- */
-export type UnblockResponse = ValueOf<typeof unblockResponse>;
-
-/**
- * Response returned when timing out a user.
- */
-export type TimeoutResponse = ValueOf<typeof timeoutResponse>;
-
-/**
- * Response returned when removing a timeout from a user.
- */
-export type RemoveTimeoutResponse = ValueOf<typeof removeTimeoutResponse>;
-
-/**
- * @typedef {BushClientEvents} VSCodePleaseDontRemove
- */
diff --git a/src/lib/extensions/discord.js/ExtendedMessage.ts b/src/lib/extensions/discord.js/ExtendedMessage.ts
deleted file mode 100644
index 4748803..0000000
--- a/src/lib/extensions/discord.js/ExtendedMessage.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { CommandUtil } from 'discord-akairo';
-import { Message, type Client } from 'discord.js';
-import { type RawMessageData } from 'discord.js/typings/rawDataTypes.js';
-
-export class ExtendedMessage<Cached extends boolean = boolean> extends Message<Cached> {
- public declare util: CommandUtil<Message>;
-
- public constructor(client: Client, data: RawMessageData) {
- super(client, data);
- this.util = new CommandUtil(client.commandHandler, this);
- }
-}
diff --git a/src/lib/extensions/discord.js/ExtendedUser.ts b/src/lib/extensions/discord.js/ExtendedUser.ts
deleted file mode 100644
index 23de523..0000000
--- a/src/lib/extensions/discord.js/ExtendedUser.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { User, type Partialize } from 'discord.js';
-
-declare module 'discord.js' {
- export interface User {
- /**
- * Indicates whether the user is an owner of the bot.
- */
- isOwner(): boolean;
- /**
- * Indicates whether the user is a superuser of the bot.
- */
- isSuperUser(): boolean;
- }
-}
-
-export type PartialBushUser = Partialize<ExtendedUser, 'username' | 'tag' | 'discriminator' | 'isOwner' | 'isSuperUser'>;
-
-/**
- * Represents a user on Discord.
- */
-export class ExtendedUser extends User {
- /**
- * Indicates whether the user is an owner of the bot.
- */
- public override isOwner(): boolean {
- return this.client.isOwner(this);
- }
-
- /**
- * Indicates whether the user is a superuser of the bot.
- */
- public override isSuperUser(): boolean {
- return this.client.isSuperUser(this);
- }
-}
diff --git a/src/lib/extensions/global.ts b/src/lib/extensions/global.ts
deleted file mode 100644
index a9020d7..0000000
--- a/src/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<T> {
- includes<S, R extends `${Extract<S, string>}`>(
- this: ReadonlyArray<R>,
- searchElement: S,
- fromIndex?: number
- ): searchElement is R & S;
- }
-}
-
-export {};
diff --git a/src/lib/index.ts b/src/lib/index.ts
deleted file mode 100644
index 3e57f9e..0000000
--- a/src/lib/index.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-export * from './common/AutoMod.js';
-export * from './common/ButtonPaginator.js';
-export * from './common/ConfirmationPrompt.js';
-export * from './common/DeleteButton.js';
-export type { BushInspectOptions } from './common/typings/BushInspectOptions.js';
-export type { CodeBlockLang } from './common/typings/CodeBlockLang.js';
-export * as Arg from './common/util/Arg.js';
-export * as Format from './common/util/Format.js';
-export * as Moderation from './common/util/Moderation.js';
-export type {
- AppealButtonId,
- CreateModLogEntryOptions,
- CreatePunishmentEntryOptions,
- PunishDMOptions,
- PunishmentTypeDM,
- PunishmentTypePresent,
- RemovePunishmentEntryOptions,
- SimpleCreateModLogEntryOptions
-} from './common/util/Moderation.js';
-export * from './extensions/discord-akairo/BushArgumentTypeCaster.js';
-export * from './extensions/discord-akairo/BushClient.js';
-export * from './extensions/discord-akairo/BushCommand.js';
-export * from './extensions/discord-akairo/BushCommandHandler.js';
-export * from './extensions/discord-akairo/BushInhibitor.js';
-export * from './extensions/discord-akairo/BushInhibitorHandler.js';
-export * from './extensions/discord-akairo/BushListener.js';
-export * from './extensions/discord-akairo/BushListenerHandler.js';
-export * from './extensions/discord-akairo/BushTask.js';
-export * from './extensions/discord-akairo/BushTaskHandler.js';
-export * from './extensions/discord-akairo/SlashMessage.js';
-export type { BushClientEvents } from './extensions/discord.js/BushClientEvents.js';
-export * from './extensions/discord.js/ExtendedGuild.js';
-export * from './extensions/discord.js/ExtendedGuildMember.js';
-export * from './extensions/discord.js/ExtendedMessage.js';
-export * from './extensions/discord.js/ExtendedUser.js';
-export * from './models/BaseModel.js';
-export * from './models/instance/ActivePunishment.js';
-export * from './models/instance/Guild.js';
-export * from './models/instance/Highlight.js';
-export * from './models/instance/Level.js';
-export * from './models/instance/ModLog.js';
-export * from './models/instance/Reminder.js';
-export * from './models/instance/StickyRole.js';
-export * from './models/shared/Global.js';
-export * from './models/shared/MemberCount.js';
-export * from './models/shared/Shared.js';
-export * from './models/shared/Stat.js';
-export * from './utils/AllowedMentions.js';
-export * from './utils/BushCache.js';
-export * from './utils/BushConstants.js';
-export * from './utils/BushLogger.js';
-export * from './utils/BushUtils.js';
-export * from './utils/CanvasProgressBar.js';
diff --git a/src/lib/models/BaseModel.ts b/src/lib/models/BaseModel.ts
deleted file mode 100644
index e503317..0000000
--- a/src/lib/models/BaseModel.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-const { Model } = (await import('sequelize')).default;
-
-export abstract class BaseModel<A, B> extends Model<A, B> {
- /**
- * The date when the row was created.
- */
- public declare readonly createdAt: Date;
-
- /**
- * The date when the row was last updated.
- */
- public declare readonly updatedAt: Date;
-}
diff --git a/src/lib/models/instance/ActivePunishment.ts b/src/lib/models/instance/ActivePunishment.ts
deleted file mode 100644
index 38012ca..0000000
--- a/src/lib/models/instance/ActivePunishment.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import { type Snowflake } from 'discord.js';
-import { nanoid } from 'nanoid';
-import { type Sequelize } from 'sequelize';
-import { BaseModel } from '../BaseModel.js';
-const { DataTypes } = (await import('sequelize')).default;
-
-export enum ActivePunishmentType {
- BAN = 'BAN',
- MUTE = 'MUTE',
- ROLE = 'ROLE',
- BLOCK = 'BLOCK'
-}
-
-export interface ActivePunishmentModel {
- id: string;
- type: ActivePunishmentType;
- user: Snowflake;
- guild: Snowflake;
- extraInfo: Snowflake;
- expires: Date | null;
- modlog: string;
-}
-
-export interface ActivePunishmentModelCreationAttributes {
- id?: string;
- type: ActivePunishmentType;
- user: Snowflake;
- guild: Snowflake;
- extraInfo?: Snowflake;
- expires?: Date;
- modlog: string;
-}
-
-/**
- * Keeps track of active punishments so they can be removed later.
- */
-export class ActivePunishment
- extends BaseModel<ActivePunishmentModel, ActivePunishmentModelCreationAttributes>
- implements ActivePunishmentModel
-{
- /**
- * The ID of this punishment (no real use just for a primary key)
- */
- public declare id: string;
-
- /**
- * The type of punishment.
- */
- public declare type: ActivePunishmentType;
-
- /**
- * The user who is punished.
- */
- public declare user: Snowflake;
-
- /**
- * The guild they are punished in.
- */
- public declare guild: Snowflake;
-
- /**
- * Additional info about the punishment if applicable. The channel id for channel blocks and role for punishment roles.
- */
- public declare extraInfo: Snowflake;
-
- /**
- * The date when this punishment expires (optional).
- */
- public declare expires: Date | null;
-
- /**
- * The reference to the modlog entry.
- */
- public declare modlog: string;
-
- /**
- * Initializes the model.
- * @param sequelize The sequelize instance.
- */
- public static initModel(sequelize: Sequelize): void {
- ActivePunishment.init(
- {
- id: { type: DataTypes.STRING, primaryKey: true, defaultValue: nanoid },
- type: { type: DataTypes.STRING, allowNull: false },
- user: { type: DataTypes.STRING, allowNull: false },
- guild: { type: DataTypes.STRING, allowNull: false, references: { model: 'Guilds', key: 'id' } },
- extraInfo: { type: DataTypes.STRING, allowNull: true },
- expires: { type: DataTypes.DATE, allowNull: true },
- modlog: { type: DataTypes.STRING, allowNull: true, references: { model: 'ModLogs', key: 'id' } }
- },
- { sequelize }
- );
- }
-}
diff --git a/src/lib/models/instance/Guild.ts b/src/lib/models/instance/Guild.ts
deleted file mode 100644
index f0cac74..0000000
--- a/src/lib/models/instance/Guild.ts
+++ /dev/null
@@ -1,422 +0,0 @@
-import { ChannelType, Constants, type Snowflake } from 'discord.js';
-import { type Sequelize } from 'sequelize';
-import { BadWordDetails } from '../../common/AutoMod.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;
- prefix: string;
- autoPublishChannels: Snowflake[];
- blacklistedChannels: Snowflake[];
- blacklistedUsers: Snowflake[];
- welcomeChannel: Snowflake | null;
- muteRole: Snowflake | null;
- punishmentEnding: string | null;
- disabledCommands: string[];
- lockdownChannels: Snowflake[];
- autoModPhases: BadWordDetails[];
- enabledFeatures: GuildFeatures[];
- joinRoles: Snowflake[];
- logChannels: LogChannelDB;
- bypassChannelBlacklist: Snowflake[];
- noXpChannels: Snowflake[];
- levelRoles: { [level: number]: Snowflake };
- levelUpChannel: Snowflake | null;
-}
-
-export interface GuildModelCreationAttributes {
- id: Snowflake;
- prefix?: string;
- autoPublishChannels?: Snowflake[];
- blacklistedChannels?: Snowflake[];
- blacklistedUsers?: Snowflake[];
- welcomeChannel?: Snowflake;
- muteRole?: Snowflake;
- punishmentEnding?: string;
- disabledCommands?: string[];
- lockdownChannels?: Snowflake[];
- autoModPhases?: BadWordDetails[];
- enabledFeatures?: GuildFeatures[];
- joinRoles?: Snowflake[];
- logChannels?: LogChannelDB;
- bypassChannelBlacklist?: Snowflake[];
- noXpChannels?: Snowflake[];
- levelRoles?: { [level: number]: Snowflake };
- levelUpChannel?: Snowflake;
-}
-
-/**
- * Settings for a guild.
- */
-export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> implements GuildModel {
- /**
- * The ID of the guild
- */
- public declare id: Snowflake;
-
- /**
- * The bot's prefix for the guild
- */
- public declare prefix: string;
-
- /**
- * Channels that will have their messages automatically published
- */
- public declare autoPublishChannels: Snowflake[];
-
- /**
- * Channels where the bot won't respond in.
- */
- public declare blacklistedChannels: Snowflake[];
-
- /**
- * Users that the bot ignores in this guild
- */
- public declare blacklistedUsers: Snowflake[];
-
- /**
- * The channels where the welcome messages are sent
- */
- public declare welcomeChannel: Snowflake | null;
-
- /**
- * The role given out when muting someone
- */
- public declare muteRole: Snowflake | null;
-
- /**
- * The message that gets sent after someone gets a punishment dm
- */
- public declare punishmentEnding: string | null;
-
- /**
- * Guild specific disabled commands
- */
- public declare disabledCommands: string[];
-
- /**
- * Channels that should get locked down when the lockdown command gets used.
- */
- public declare lockdownChannels: Snowflake[];
-
- /**
- * Custom automod phases
- */
- public declare autoModPhases: BadWordDetails[];
-
- /**
- * The features enabled in a guild
- */
- public declare enabledFeatures: GuildFeatures[];
-
- /**
- * The roles to assign to a user if they are not assigned sticky roles
- */
- public declare joinRoles: Snowflake[];
-
- /**
- * The channels where logging messages will be sent.
- */
- public declare logChannels: LogChannelDB;
-
- /**
- * These users will be able to use commands in channels blacklisted
- */
- public declare bypassChannelBlacklist: Snowflake[];
-
- /**
- * Channels where users will not earn xp for leveling.
- */
- public declare noXpChannels: Snowflake[];
-
- /**
- * What roles get given to users when they reach certain levels.
- */
- public declare levelRoles: { [level: number]: Snowflake };
-
- /**
- * The channel to send level up messages in instead of last channel.
- */
- public declare levelUpChannel: Snowflake | null;
-
- /**
- * Initializes the model.
- * @param sequelize The sequelize instance.
- */
- public static initModel(sequelize: Sequelize, client: BushClient): void {
- Guild.init(
- {
- id: { type: DataTypes.STRING, primaryKey: true },
- prefix: { type: DataTypes.TEXT, allowNull: false, defaultValue: client.config.prefix },
- autoPublishChannels: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- blacklistedChannels: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- blacklistedUsers: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- welcomeChannel: { type: DataTypes.STRING, allowNull: true },
- muteRole: { type: DataTypes.STRING, allowNull: true },
- punishmentEnding: { type: DataTypes.TEXT, allowNull: true },
- disabledCommands: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- lockdownChannels: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- autoModPhases: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- enabledFeatures: {
- type: DataTypes.JSONB,
- allowNull: false,
- defaultValue: Object.keys(guildFeaturesObj).filter(
- (key) => guildFeaturesObj[key as keyof typeof guildFeaturesObj].default
- )
- },
- joinRoles: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- logChannels: { type: DataTypes.JSONB, allowNull: false, defaultValue: {} },
- bypassChannelBlacklist: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- noXpChannels: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- levelRoles: { type: DataTypes.JSONB, allowNull: false, defaultValue: {} },
- levelUpChannel: { type: DataTypes.STRING, allowNull: true }
- },
- { sequelize }
- );
- }
-}
-
-export type BaseGuildSetting = 'channel' | 'role' | 'user';
-export type GuildNoArraySetting = 'string' | 'custom' | BaseGuildSetting;
-export type GuildSettingType = GuildNoArraySetting | `${BaseGuildSetting}-array`;
-
-export interface GuildSetting {
- name: string;
- description: string;
- type: GuildSettingType;
- subType: ChannelType[] | undefined;
- configurable: boolean;
- replaceNullWith: () => string | null;
-}
-const asGuildSetting = <T>(et: { [K in keyof T]: PartialBy<GuildSetting, 'configurable' | 'subType' | 'replaceNullWith'> }) => {
- for (const key in et) {
- et[key].subType ??= undefined;
- et[key].configurable ??= true;
- et[key].replaceNullWith ??= () => null;
- }
- return et as { [K in keyof T]: GuildSetting };
-};
-
-const { default: config } = await import('../../../../config/options.js');
-
-export const guildSettingsObj = asGuildSetting({
- prefix: {
- name: 'Prefix',
- description: 'The phrase required to trigger text commands in this server.',
- type: 'string',
- replaceNullWith: () => config.prefix
- },
- autoPublishChannels: {
- name: 'Auto Publish Channels',
- description: 'Channels were every message is automatically published.',
- type: 'channel-array',
- subType: [ChannelType.GuildNews]
- },
- welcomeChannel: {
- name: 'Welcome Channel',
- description: 'The channel where the bot will send join and leave message.',
- type: 'channel',
- subType: [
- ChannelType.GuildText,
- ChannelType.GuildNews,
- ChannelType.GuildNewsThread,
- ChannelType.GuildPublicThread,
- ChannelType.GuildPrivateThread
- ]
- },
- muteRole: {
- name: 'Mute Role',
- description: 'The role assigned when muting someone.',
- type: 'role'
- },
- punishmentEnding: {
- name: 'Punishment Ending',
- description: 'The message after punishment information to a user in a dm.',
- type: 'string'
- },
- lockdownChannels: {
- name: 'Lockdown Channels',
- description: 'Channels that are locked down when a mass lockdown is specified.',
- type: 'channel-array',
- subType: [ChannelType.GuildText]
- },
- joinRoles: {
- name: 'Join Roles',
- description: 'Roles assigned to users on join who do not have sticky role information.',
- type: 'role-array'
- },
- bypassChannelBlacklist: {
- name: 'Bypass Channel Blacklist',
- description: 'These users will be able to use commands in channels blacklisted.',
- type: 'user-array'
- },
- logChannels: {
- name: 'Log Channels',
- description: 'The channel were logs are sent.',
- type: 'custom',
- subType: [ChannelType.GuildText],
- configurable: false
- },
- autoModPhases: {
- name: 'Automod Phases',
- description: 'Custom phrases to be detected by automod.',
- type: 'custom',
- configurable: false
- },
- noXpChannels: {
- name: 'No Xp Channels',
- description: 'Channels where users will not earn xp for leveling.',
- type: 'channel-array',
- subType: Constants.TextBasedChannelTypes.filter((type) => type !== ChannelType.DM)
- },
- levelRoles: {
- name: 'Level Roles',
- description: 'What roles get given to users when they reach certain levels.',
- type: 'custom',
- configurable: false
- },
- levelUpChannel: {
- name: 'Level Up Channel',
- description: 'The channel to send level up messages in instead of last channel.',
- type: 'channel',
- subType: Constants.TextBasedChannelTypes.filter((type) => type !== ChannelType.DM)
- }
-});
-
-export type GuildSettings = keyof typeof guildSettingsObj;
-export const settingsArr = Object.keys(guildSettingsObj).filter(
- (s) => guildSettingsObj[s as GuildSettings].configurable
-) as GuildSettings[];
-
-interface GuildFeature {
- name: string;
- description: string;
- default: boolean;
- hidden: boolean;
-}
-
-type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
-
-const asGuildFeature = <T>(gf: { [K in keyof T]: PartialBy<GuildFeature, 'hidden' | 'default'> }): {
- [K in keyof T]: GuildFeature;
-} => {
- for (const key in gf) {
- gf[key].hidden ??= false;
- gf[key].default ??= false;
- }
- return gf as { [K in keyof T]: GuildFeature };
-};
-
-export const guildFeaturesObj = asGuildFeature({
- automod: {
- name: 'Automod',
- description: 'Deletes offensive content as well as phishing links.'
- },
- excludeDefaultAutomod: {
- name: 'Exclude Default Automod',
- description: 'Opt out of using the default automod options.'
- },
- excludeAutomodScamLinks: {
- name: 'Exclude Automod Scam Links',
- description: 'Opt out of having automod delete scam links.'
- },
- delScamMentions: {
- name: 'Delete Scam Mentions',
- description: 'Deletes messages with @everyone and @here mentions that have common scam phrases.'
- },
- blacklistedFile: {
- name: 'Blacklisted File',
- description: 'Automatically deletes malicious files.'
- },
- autoPublish: {
- name: 'Auto Publish',
- description: 'Publishes messages in configured announcement channels.'
- },
- // todo implement a better auto thread system
- autoThread: {
- name: 'Auto Thread',
- description: 'Creates a new thread for messages in configured channels.',
- hidden: true
- },
- perspectiveApi: {
- name: 'Perspective API',
- description: 'Use the Perspective API to detect toxicity.',
- hidden: true
- },
- boosterMessageReact: {
- name: 'Booster Message React',
- description: 'Reacts to booster messages with the boost emoji.'
- },
- leveling: {
- name: 'Leveling',
- description: "Tracks users' messages and assigns them xp."
- },
- sendLevelUpMessages: {
- name: 'Send Level Up Messages',
- description: 'Send a message when a user levels up.',
- default: true
- },
- stickyRoles: {
- name: 'Sticky Roles',
- description: 'Restores past roles to a user when they rejoin.'
- },
- reporting: {
- name: 'Reporting',
- description: 'Allow users to make reports.'
- },
- modsCanPunishMods: {
- name: 'Mods Can Punish Mods',
- description: 'Allow moderators to punish other moderators.'
- },
- logManualPunishments: {
- name: 'Log Manual Punishments',
- description: "Adds manual punishment to the user's modlogs and the logging channels.",
- default: true
- },
- punishmentAppeals: {
- name: 'Punishment Appeals',
- description: 'Allow users to appeal their punishments and send the appeal to the configured channel.',
- hidden: true
- },
- highlight: {
- name: 'Highlight',
- description: 'Allows the highlight command to be used.',
- default: true
- }
-});
-
-export const guildLogsObj = {
- automod: {
- description: 'Sends a message in this channel every time automod is activated.',
- configurable: true
- },
- moderation: {
- description: 'Sends a message in this channel every time a moderation action is performed.',
- configurable: true
- },
- report: {
- description: 'Logs user reports.',
- configurable: true
- },
- error: {
- description: 'Logs errors that occur with the bot.',
- configurable: true
- },
- appeals: {
- description: 'Where punishment appeals are sent.',
- configurable: false
- }
-};
-
-export type GuildLogType = keyof typeof guildLogsObj;
-export const guildLogsArr = Object.keys(guildLogsObj).filter(
- (s) => guildLogsObj[s as GuildLogType].configurable
-) as GuildLogType[];
-type LogChannelDB = { [x in keyof typeof guildLogsObj]?: Snowflake };
-
-export type GuildFeatures = keyof typeof guildFeaturesObj;
-export const guildFeaturesArr: GuildFeatures[] = Object.keys(guildFeaturesObj).filter(
- (f) => !guildFeaturesObj[f as keyof typeof guildFeaturesObj].hidden
-) as GuildFeatures[];
diff --git a/src/lib/models/instance/Highlight.ts b/src/lib/models/instance/Highlight.ts
deleted file mode 100644
index 5889fad..0000000
--- a/src/lib/models/instance/Highlight.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import { type Snowflake } from 'discord.js';
-import { nanoid } from 'nanoid';
-import { type Sequelize } from 'sequelize';
-import { BaseModel } from '../BaseModel.js';
-const { DataTypes } = (await import('sequelize')).default;
-
-export interface HighlightModel {
- pk: string;
- user: Snowflake;
- guild: Snowflake;
- words: HighlightWord[];
- blacklistedChannels: Snowflake[];
- blacklistedUsers: Snowflake[];
-}
-
-export interface HighLightCreationAttributes {
- pk?: string;
- user: Snowflake;
- guild: Snowflake;
- words?: HighlightWord[];
- blacklistedChannels?: Snowflake[];
- blacklistedUsers?: Snowflake[];
-}
-
-export interface HighlightWord {
- word: string;
- regex: boolean;
-}
-
-/**
- * List of words that should cause the user to be notified for if found in the specified guild.
- */
-export class Highlight extends BaseModel<HighlightModel, HighLightCreationAttributes> implements HighlightModel {
- /**
- * The primary key of the highlight.
- */
- public declare pk: string;
-
- /**
- * The user that the highlight is for.
- */
- public declare user: Snowflake;
-
- /**
- * The guild to look for highlights in.
- */
- public declare guild: Snowflake;
-
- /**
- * The words to look for.
- */
- public declare words: HighlightWord[];
-
- /**
- * Channels that the user choose to ignore highlights in.
- */
- public declare blacklistedChannels: Snowflake[];
-
- /**
- * Users that the user choose to ignore highlights from.
- */
- public declare blacklistedUsers: Snowflake[];
-
- /**
- * Initializes the model.
- * @param sequelize The sequelize instance.
- */
- public static initModel(sequelize: Sequelize): void {
- Highlight.init(
- {
- pk: { type: DataTypes.STRING, primaryKey: true, defaultValue: nanoid },
- user: { type: DataTypes.STRING, allowNull: false },
- guild: { type: DataTypes.STRING, allowNull: false },
- words: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- blacklistedChannels: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- blacklistedUsers: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }
- },
- { sequelize }
- );
- }
-}
diff --git a/src/lib/models/instance/Level.ts b/src/lib/models/instance/Level.ts
deleted file mode 100644
index d8d16f0..0000000
--- a/src/lib/models/instance/Level.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { type Snowflake } from 'discord.js';
-import { type Sequelize } from 'sequelize';
-import { BaseModel } from '../BaseModel.js';
-const { DataTypes } = (await import('sequelize')).default;
-
-export interface LevelModel {
- user: Snowflake;
- guild: Snowflake;
- xp: number;
-}
-
-export interface LevelModelCreationAttributes {
- user: Snowflake;
- guild: Snowflake;
- xp?: number;
-}
-
-/**
- * Leveling information for a user in a guild.
- */
-export class Level extends BaseModel<LevelModel, LevelModelCreationAttributes> implements LevelModel {
- /**
- * The user's id.
- */
- public declare user: Snowflake;
-
- /**
- * The guild where the user is gaining xp.
- */
- public declare guild: Snowflake;
-
- /**
- * The user's xp.
- */
- public declare xp: number;
-
- /**
- * The user's level.
- */
- public get level(): number {
- return Level.convertXpToLevel(this.xp);
- }
-
- /**
- * Initializes the model.
- * @param sequelize The sequelize instance.
- */
- public static initModel(sequelize: Sequelize): void {
- Level.init(
- {
- user: { type: DataTypes.STRING, allowNull: false },
- guild: { type: DataTypes.STRING, allowNull: false },
- xp: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0 }
- },
- { sequelize }
- );
- }
-
- public static convertXpToLevel(xp: number): number {
- return Math.floor((-25 + Math.sqrt(625 + 200 * xp)) / 100);
- }
-
- public static convertLevelToXp(level: number): number {
- return 50 * level * level + 25 * level; // 50x² + 25x
- }
-
- public static genRandomizedXp(): number {
- return Math.floor(Math.random() * (40 - 15 + 1)) + 15;
- }
-}
diff --git a/src/lib/models/instance/ModLog.ts b/src/lib/models/instance/ModLog.ts
deleted file mode 100644
index c25f043..0000000
--- a/src/lib/models/instance/ModLog.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-import { type Snowflake } from 'discord.js';
-import { nanoid } from 'nanoid';
-import { type Sequelize } from 'sequelize';
-import { BaseModel } from '../BaseModel.js';
-const { DataTypes } = (await import('sequelize')).default;
-
-export enum ModLogType {
- PERM_BAN = 'PERM_BAN',
- TEMP_BAN = 'TEMP_BAN',
- UNBAN = 'UNBAN',
- KICK = 'KICK',
- PERM_MUTE = 'PERM_MUTE',
- TEMP_MUTE = 'TEMP_MUTE',
- UNMUTE = 'UNMUTE',
- WARN = 'WARN',
- PERM_PUNISHMENT_ROLE = 'PERM_PUNISHMENT_ROLE',
- TEMP_PUNISHMENT_ROLE = 'TEMP_PUNISHMENT_ROLE',
- REMOVE_PUNISHMENT_ROLE = 'REMOVE_PUNISHMENT_ROLE',
- PERM_CHANNEL_BLOCK = 'PERM_CHANNEL_BLOCK',
- TEMP_CHANNEL_BLOCK = 'TEMP_CHANNEL_BLOCK',
- CHANNEL_UNBLOCK = 'CHANNEL_UNBLOCK',
- TIMEOUT = 'TIMEOUT',
- REMOVE_TIMEOUT = 'REMOVE_TIMEOUT'
-}
-
-export interface ModLogModel {
- id: string;
- type: ModLogType;
- user: Snowflake;
- moderator: Snowflake;
- reason: string | null;
- duration: number | null;
- guild: Snowflake;
- evidence: string;
- pseudo: boolean;
- hidden: boolean;
-}
-
-export interface ModLogModelCreationAttributes {
- id?: string;
- type: ModLogType;
- user: Snowflake;
- moderator: Snowflake;
- reason?: string | null;
- duration?: number;
- guild: Snowflake;
- evidence?: string;
- pseudo?: boolean;
- hidden?: boolean;
-}
-
-/**
- * A mod log case.
- */
-export class ModLog extends BaseModel<ModLogModel, ModLogModelCreationAttributes> implements ModLogModel {
- /**
- * The primary key of the modlog entry.
- */
- public declare id: string;
-
- /**
- * The type of punishment.
- */
- public declare type: ModLogType;
-
- /**
- * The user being punished.
- */
- public declare user: Snowflake;
-
- /**
- * The user carrying out the punishment.
- */
- public declare moderator: Snowflake;
-
- /**
- * The reason the user is getting punished.
- */
- public declare reason: string | null;
-
- /**
- * The amount of time the user is getting punished for.
- */
- public declare duration: number | null;
-
- /**
- * The guild the user is getting punished in.
- */
- public declare guild: Snowflake;
-
- /**
- * Evidence of what the user is getting punished for.
- */
- public declare evidence: string;
-
- /**
- * Not an actual modlog just used so a punishment entry can be made.
- */
- public declare pseudo: boolean;
-
- /**
- * Hides from the modlog command unless show hidden is specified.
- */
- public declare hidden: boolean;
-
- /**
- * Initializes the model.
- * @param sequelize The sequelize instance.
- */
- public static initModel(sequelize: Sequelize): void {
- ModLog.init(
- {
- id: { type: DataTypes.STRING, primaryKey: true, allowNull: false, defaultValue: nanoid },
- type: { type: DataTypes.STRING, allowNull: false }, //? This is not an enum because of a sequelize issue: https://github.com/sequelize/sequelize/issues/2554
- user: { type: DataTypes.STRING, allowNull: false },
- moderator: { type: DataTypes.STRING, allowNull: false },
- duration: { type: DataTypes.STRING, allowNull: true },
- reason: { type: DataTypes.TEXT, allowNull: true },
- guild: { type: DataTypes.STRING, references: { model: 'Guilds', key: 'id' } },
- evidence: { type: DataTypes.TEXT, allowNull: true },
- pseudo: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
- hidden: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false }
- },
- { sequelize }
- );
- }
-}
diff --git a/src/lib/models/instance/Reminder.ts b/src/lib/models/instance/Reminder.ts
deleted file mode 100644
index 964ea63..0000000
--- a/src/lib/models/instance/Reminder.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import { Snowflake } from 'discord.js';
-import { nanoid } from 'nanoid';
-import { type Sequelize } from 'sequelize';
-import { BaseModel } from '../BaseModel.js';
-const { DataTypes } = (await import('sequelize')).default;
-
-export interface ReminderModel {
- id: string;
- user: Snowflake;
- messageUrl: string;
- content: string;
- created: Date;
- expires: Date;
- notified: boolean;
-}
-
-export interface ReminderModelCreationAttributes {
- id?: string;
- user: Snowflake;
- messageUrl: string;
- content: string;
- created: Date;
- expires: Date;
- notified?: boolean;
-}
-
-/**
- * Represents a reminder the a user has set.
- */
-export class Reminder extends BaseModel<ReminderModel, ReminderModelCreationAttributes> implements ReminderModel {
- /**
- * The id of the reminder.
- */
- public declare id: string;
-
- /**
- * The user that the reminder is for.
- */
- public declare user: Snowflake;
-
- /**
- * The url of the message where the reminder was created.
- */
- public declare messageUrl: string;
-
- /**
- * The content of the reminder.
- */
- public declare content: string;
-
- /**
- * The date the reminder was created.
- */
- public declare created: Date;
-
- /**
- * The date when the reminder expires.
- */
- public declare expires: Date;
-
- /**
- * Whether the user has been notified about the reminder.
- */
- public declare notified: boolean;
-
- /**
- * Initializes the model.
- * @param sequelize The sequelize instance.
- */
- public static initModel(sequelize: Sequelize): void {
- Reminder.init(
- {
- id: { type: DataTypes.STRING, primaryKey: true, defaultValue: nanoid },
- user: { type: DataTypes.STRING, allowNull: false },
- messageUrl: { type: DataTypes.STRING, allowNull: false },
- content: { type: DataTypes.TEXT, allowNull: false },
- created: { type: DataTypes.DATE, allowNull: false },
- expires: { type: DataTypes.DATE, allowNull: false },
- notified: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false }
- },
- { sequelize }
- );
- }
-}
diff --git a/src/lib/models/instance/StickyRole.ts b/src/lib/models/instance/StickyRole.ts
deleted file mode 100644
index 00e98ce..0000000
--- a/src/lib/models/instance/StickyRole.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { type Snowflake } from 'discord.js';
-import { type Sequelize } from 'sequelize';
-import { BaseModel } from '../BaseModel.js';
-const { DataTypes } = (await import('sequelize')).default;
-
-export interface StickyRoleModel {
- user: Snowflake;
- guild: Snowflake;
- roles: Snowflake[];
- nickname: string;
-}
-export interface StickyRoleModelCreationAttributes {
- user: Snowflake;
- guild: Snowflake;
- roles: Snowflake[];
- nickname?: string;
-}
-
-/**
- * Information about a user's roles and nickname when they leave a guild.
- */
-export class StickyRole extends BaseModel<StickyRoleModel, StickyRoleModelCreationAttributes> implements StickyRoleModel {
- /**
- * The id of the user the roles belongs to.
- */
- public declare user: Snowflake;
-
- /**
- * The guild where this should happen.
- */
- public declare guild: Snowflake;
-
- /**
- * The roles that the user should have returned
- */
- public declare roles: Snowflake[];
-
- /**
- * The user's previous nickname
- */
- public declare nickname: string;
-
- /**
- * Initializes the model.
- * @param sequelize The sequelize instance.
- */
- public static initModel(sequelize: Sequelize): void {
- StickyRole.init(
- {
- user: { type: DataTypes.STRING, allowNull: false },
- guild: { type: DataTypes.STRING, allowNull: false },
- roles: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- nickname: { type: DataTypes.STRING, allowNull: true }
- },
- { sequelize }
- );
- }
-}
diff --git a/src/lib/models/shared/Global.ts b/src/lib/models/shared/Global.ts
deleted file mode 100644
index b1aa0cc..0000000
--- a/src/lib/models/shared/Global.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { type Snowflake } from 'discord.js';
-import { type Sequelize } from 'sequelize';
-import { BaseModel } from '../BaseModel.js';
-const { DataTypes } = (await import('sequelize')).default;
-
-export interface GlobalModel {
- environment: 'production' | 'development' | 'beta';
- disabledCommands: string[];
- blacklistedUsers: Snowflake[];
- blacklistedGuilds: Snowflake[];
- blacklistedChannels: Snowflake[];
-}
-
-export interface GlobalModelCreationAttributes {
- environment: 'production' | 'development' | 'beta';
- disabledCommands?: string[];
- blacklistedUsers?: Snowflake[];
- blacklistedGuilds?: Snowflake[];
- blacklistedChannels?: Snowflake[];
-}
-
-/**
- * Data specific to a certain instance of the bot.
- */
-export class Global extends BaseModel<GlobalModel, GlobalModelCreationAttributes> implements GlobalModel {
- /**
- * The bot's environment.
- */
- public declare environment: 'production' | 'development' | 'beta';
-
- /**
- * Globally disabled commands.
- */
- public declare disabledCommands: string[];
-
- /**
- * Globally blacklisted users.
- */
- public declare blacklistedUsers: Snowflake[];
-
- /**
- * Guilds blacklisted from using the bot.
- */
- public declare blacklistedGuilds: Snowflake[];
-
- /**
- * Channels where the bot is prevented from running commands in.
- */
- public declare blacklistedChannels: Snowflake[];
-
- /**
- * Initializes the model.
- * @param sequelize The sequelize instance.
- */
- public static initModel(sequelize: Sequelize): void {
- Global.init(
- {
- environment: { type: DataTypes.STRING, primaryKey: true },
- disabledCommands: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- blacklistedUsers: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- blacklistedGuilds: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- blacklistedChannels: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }
- },
- { sequelize }
- );
- }
-}
diff --git a/src/lib/models/shared/GuildCount.ts b/src/lib/models/shared/GuildCount.ts
deleted file mode 100644
index 51e571a..0000000
--- a/src/lib/models/shared/GuildCount.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { type Sequelize } from 'sequelize';
-import { Environment } from '../../../../config/Config.js';
-const { DataTypes, Model } = (await import('sequelize')).default;
-
-export interface GuildCountModel {
- timestamp: Date;
- environment: Environment;
- guildCount: number;
-}
-
-export interface GuildCountCreationAttributes {
- timestamp?: Date;
- environment: Environment;
- guildCount: number;
-}
-
-/**
- * The number of guilds that the bot is in for each environment.
- */
-export class GuildCount extends Model<GuildCountModel, GuildCountCreationAttributes> implements GuildCountModel {
- public declare timestamp: Date;
- public declare environment: Environment;
- public declare guildCount: number;
-
- /**
- * Initializes the model.
- * @param sequelize The sequelize instance.
- */
- public static initModel(sequelize: Sequelize): void {
- GuildCount.init(
- {
- timestamp: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
- environment: { type: DataTypes.STRING, allowNull: false },
- guildCount: { type: DataTypes.BIGINT, allowNull: false }
- },
- { sequelize, timestamps: false }
- );
- }
-}
diff --git a/src/lib/models/shared/MemberCount.ts b/src/lib/models/shared/MemberCount.ts
deleted file mode 100644
index ea8795c..0000000
--- a/src/lib/models/shared/MemberCount.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { type Sequelize } from 'sequelize';
-const { DataTypes, Model } = (await import('sequelize')).default;
-
-export interface MemberCountModel {
- timestamp: Date;
- guildId: string;
- memberCount: number;
-}
-
-export interface MemberCountCreationAttributes {
- timestamp?: Date;
- guildId: string;
- memberCount: number;
-}
-
-/**
- * The member count of each guild that the bot is in that have over 100 members.
- */
-export class MemberCount extends Model<MemberCountModel, MemberCountCreationAttributes> implements MemberCountModel {
- public declare timestamp: Date;
- public declare guildId: string;
- public declare memberCount: number;
-
- /**
- * Initializes the model.
- * @param sequelize The sequelize instance.
- */
- public static initModel(sequelize: Sequelize): void {
- MemberCount.init(
- {
- timestamp: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
- guildId: { type: DataTypes.STRING, allowNull: false },
- memberCount: { type: DataTypes.BIGINT, allowNull: false }
- },
- { sequelize, timestamps: false }
- );
- }
-}
diff --git a/src/lib/models/shared/Shared.ts b/src/lib/models/shared/Shared.ts
deleted file mode 100644
index 4d13011..0000000
--- a/src/lib/models/shared/Shared.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import { Snowflake } from 'discord.js';
-import type { Sequelize } from 'sequelize';
-import type { BadWords } from '../../common/AutoMod.js';
-import { BaseModel } from '../BaseModel.js';
-const { DataTypes } = (await import('sequelize')).default;
-
-export interface SharedModel {
- primaryKey: 0;
- superUsers: Snowflake[];
- privilegedUsers: Snowflake[];
- badLinksSecret: string[];
- badLinks: string[];
- badWords: BadWords;
- autoBanCode: string | null;
-}
-
-export interface SharedModelCreationAttributes {
- primaryKey?: 0;
- superUsers?: Snowflake[];
- privilegedUsers?: Snowflake[];
- badLinksSecret?: string[];
- badLinks?: string[];
- badWords?: BadWords;
- autoBanCode?: string;
-}
-
-/**
- * Data shared between all bot instances.
- */
-export class Shared extends BaseModel<SharedModel, SharedModelCreationAttributes> implements SharedModel {
- /**
- * The primary key of the shared model.
- */
- public declare primaryKey: 0;
-
- /**
- * Trusted users.
- */
- public declare superUsers: Snowflake[];
-
- /**
- * Users that have all permissions that devs have except eval.
- */
- public declare privilegedUsers: Snowflake[];
-
- /**
- * Non-public bad links.
- */
- public declare badLinksSecret: string[];
-
- /**
- * Public Bad links.
- */
- public declare badLinks: string[];
-
- /**
- * Bad words.
- */
- public declare badWords: BadWords;
-
- /**
- * Code that is used to match for auto banning users in moulberry's bush
- */
- public declare autoBanCode: string;
-
- /**
- * Initializes the model.
- * @param sequelize The sequelize instance.
- */
- public static initModel(sequelize: Sequelize): void {
- Shared.init(
- {
- primaryKey: { type: DataTypes.INTEGER, primaryKey: true, validate: { min: 0, max: 0 } },
- superUsers: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- privilegedUsers: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- badLinksSecret: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- badLinks: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] },
- badWords: { type: DataTypes.JSONB, allowNull: false, defaultValue: {} },
- autoBanCode: { type: DataTypes.TEXT }
- },
- { sequelize, freezeTableName: true }
- );
- }
-}
diff --git a/src/lib/models/shared/Stat.ts b/src/lib/models/shared/Stat.ts
deleted file mode 100644
index 8e2e0b3..0000000
--- a/src/lib/models/shared/Stat.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import { type Sequelize } from 'sequelize';
-import { BaseModel } from '../BaseModel.js';
-const { DataTypes } = (await import('sequelize')).default;
-
-type Environment = 'production' | 'development' | 'beta';
-
-export interface StatModel {
- environment: Environment;
- commandsUsed: bigint;
- slashCommandsUsed: bigint;
-}
-
-export interface StatModelCreationAttributes {
- environment: Environment;
- commandsUsed?: bigint;
- slashCommandsUsed?: bigint;
-}
-
-/**
- * Statistics for each instance of the bot.
- */
-export class Stat extends BaseModel<StatModel, StatModelCreationAttributes> implements StatModel {
- /**
- * The bot's environment.
- */
- public declare environment: Environment;
-
- /**
- * The number of commands used
- */
- public declare commandsUsed: bigint;
-
- /**
- * The number of slash commands used
- */
- public declare slashCommandsUsed: bigint;
-
- /**
- * Initializes the model.
- * @param sequelize The sequelize instance.
- */
- public static initModel(sequelize: Sequelize): void {
- Stat.init(
- {
- environment: { type: DataTypes.STRING, primaryKey: true },
- commandsUsed: {
- type: DataTypes.TEXT,
- get: function (): bigint {
- return BigInt(this.getDataValue('commandsUsed'));
- },
- set: function (val: bigint) {
- return this.setDataValue('commandsUsed', <any>`${val}`);
- },
- allowNull: false,
- defaultValue: `${0n}`
- },
- slashCommandsUsed: {
- type: DataTypes.TEXT,
- get: function (): bigint {
- return BigInt(this.getDataValue('slashCommandsUsed'));
- },
- set: function (val: bigint) {
- return this.setDataValue('slashCommandsUsed', <any>`${val}`);
- },
- allowNull: false,
- defaultValue: `${0n}`
- }
- },
- { sequelize }
- );
- }
-}
diff --git a/src/lib/utils/AllowedMentions.ts b/src/lib/utils/AllowedMentions.ts
deleted file mode 100644
index d2eb030..0000000
--- a/src/lib/utils/AllowedMentions.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { type MessageMentionOptions, type MessageMentionTypes } from 'discord.js';
-
-/**
- * A utility class for creating allowed mentions.
- */
-export class AllowedMentions {
- /**
- * @param everyone Whether everyone and here should be mentioned.
- * @param roles Whether roles should be mentioned.
- * @param users Whether users should be mentioned.
- * @param repliedUser Whether the author of the Message being replied to should be mentioned.
- */
- public constructor(public everyone = false, public roles = false, public users = true, public repliedUser = true) {}
-
- /**
- * Don't mention anyone.
- * @param repliedUser Whether the author of the Message being replied to should be mentioned.
- */
- public static none(repliedUser = true): MessageMentionOptions {
- return { parse: [], repliedUser };
- }
-
- /**
- * Mention @everyone and @here, roles, and users.
- * @param repliedUser Whether the author of the Message being replied to should be mentioned.
- */
- public static all(repliedUser = true): MessageMentionOptions {
- return { parse: ['everyone', 'roles', 'users'], repliedUser };
- }
-
- /**
- * Mention users.
- * @param repliedUser Whether the author of the Message being replied to should be mentioned.
- */
- public static users(repliedUser = true): MessageMentionOptions {
- return { parse: ['users'], repliedUser };
- }
-
- /**
- * Mention everyone and here.
- * @param repliedUser Whether the author of the Message being replied to should be mentioned.
- */
- public static everyone(repliedUser = true): MessageMentionOptions {
- return { parse: ['everyone'], repliedUser };
- }
-
- /**
- * Mention roles.
- * @param repliedUser Whether the author of the Message being replied to should be mentioned.
- */
- public static roles(repliedUser = true): MessageMentionOptions {
- return { parse: ['roles'], repliedUser };
- }
-
- /**
- * Converts this into a MessageMentionOptions object.
- */
- public toObject(): MessageMentionOptions {
- return {
- parse: [
- ...(this.users ? ['users'] : []),
- ...(this.roles ? ['roles'] : []),
- ...(this.everyone ? ['everyone'] : [])
- ] as MessageMentionTypes[],
- repliedUser: this.repliedUser
- };
- }
-}
diff --git a/src/lib/utils/BushCache.ts b/src/lib/utils/BushCache.ts
deleted file mode 100644
index 22a13ef..0000000
--- a/src/lib/utils/BushCache.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { BadWords, GlobalModel, SharedModel, type Guild } from '#lib';
-import { Collection, type Snowflake } from 'discord.js';
-
-export class BushCache {
- public global = new GlobalCache();
- public shared = new SharedCache();
- public guilds = new GuildCache();
-}
-
-export class GlobalCache implements Omit<GlobalModel, 'environment'> {
- public disabledCommands: string[] = [];
- public blacklistedChannels: Snowflake[] = [];
- public blacklistedGuilds: Snowflake[] = [];
- public blacklistedUsers: Snowflake[] = [];
-}
-
-export class SharedCache implements Omit<SharedModel, 'primaryKey'> {
- public superUsers: Snowflake[] = [];
- public privilegedUsers: Snowflake[] = [];
- public badLinksSecret: string[] = [];
- public badLinks: string[] = [];
- public badWords: BadWords = {};
- public autoBanCode: string | null = null;
-}
-
-export class GuildCache extends Collection<Snowflake, Guild> {}
diff --git a/src/lib/utils/BushClientUtils.ts b/src/lib/utils/BushClientUtils.ts
deleted file mode 100644
index 920ff40..0000000
--- a/src/lib/utils/BushClientUtils.ts
+++ /dev/null
@@ -1,498 +0,0 @@
-import assert from 'assert/strict';
-import {
- cleanCodeBlockContent,
- DMChannel,
- escapeCodeBlock,
- GuildMember,
- Message,
- PartialDMChannel,
- Routes,
- TextBasedChannel,
- ThreadMember,
- User,
- type APIMessage,
- type Client,
- type Snowflake,
- type UserResolvable
-} from 'discord.js';
-import got from 'got';
-import _ from 'lodash';
-import { ConfigChannelKey } from '../../../config/Config.js';
-import CommandErrorListener from '../../listeners/commands/commandError.js';
-import { BushInspectOptions } from '../common/typings/BushInspectOptions.js';
-import { CodeBlockLang } from '../common/typings/CodeBlockLang.js';
-import { CommandMessage } from '../extensions/discord-akairo/BushCommand.js';
-import { SlashMessage } from '../extensions/discord-akairo/SlashMessage.js';
-import { Global } from '../models/shared/Global.js';
-import { Shared } from '../models/shared/Shared.js';
-import { GlobalCache, SharedCache } from './BushCache.js';
-import { emojis, Pronoun, PronounCode, pronounMapping, regex } from './BushConstants.js';
-import { addOrRemoveFromArray, formatError, inspect } from './BushUtils.js';
-
-/**
- * Utilities that require access to the client.
- */
-export class BushClientUtils {
- /**
- * The hastebin urls used to post to hastebin, attempts to post in order
- */
- #hasteURLs: string[] = [
- 'https://hst.sh',
- // 'https://hasteb.in',
- 'https://hastebin.com',
- 'https://mystb.in',
- 'https://haste.clicksminuteper.net',
- 'https://paste.pythondiscord.com',
- 'https://haste.unbelievaboat.com'
- // 'https://haste.tyman.tech'
- ];
-
- public constructor(private readonly client: Client) {}
-
- /**
- * Maps an array of user ids to user objects.
- * @param ids The list of IDs to map
- * @returns The list of users mapped
- */
- public async mapIDs(ids: Snowflake[]): Promise<User[]> {
- return await Promise.all(ids.map((id) => this.client.users.fetch(id)));
- }
-
- /**
- * Posts text to hastebin
- * @param content The text to post
- * @returns The url of the posted text
- */
- public async haste(content: string, substr = false): Promise<HasteResults> {
- let isSubstr = false;
- if (content.length > 400_000 && !substr) {
- void this.handleError('haste', new Error(`content over 400,000 characters (${content.length.toLocaleString()})`));
- return { error: 'content too long' };
- } else if (content.length > 400_000) {
- content = content.substring(0, 400_000);
- isSubstr = true;
- }
- for (const url of this.#hasteURLs) {
- try {
- const res: HastebinRes = await got.post(`${url}/documents`, { body: content }).json();
- return { url: `${url}/${res.key}`, error: isSubstr ? 'substr' : undefined };
- } catch {
- void this.client.console.error('haste', `Unable to upload haste to ${url}`);
- }
- }
- return { error: 'unable to post' };
- }
-
- /**
- * Resolves a user-provided string into a user object, if possible
- * @param text The text to try and resolve
- * @returns The user resolved or null
- */
- public async resolveUserAsync(text: string): Promise<User | null> {
- const idReg = /\d{17,19}/;
- const idMatch = text.match(idReg);
- if (idMatch) {
- try {
- return await this.client.users.fetch(text as Snowflake);
- } catch {}
- }
- const mentionReg = /<@!?(?<id>\d{17,19})>/;
- const mentionMatch = text.match(mentionReg);
- if (mentionMatch) {
- try {
- return await this.client.users.fetch(mentionMatch.groups!.id as Snowflake);
- } catch {}
- }
- const user = this.client.users.cache.find((u) => u.username === text);
- if (user) return user;
- return null;
- }
-
- /**
- * Surrounds text in a code block with the specified language and puts it in a hastebin if its too long.
- * * Embed Description Limit = 4096 characters
- * * Embed Field Limit = 1024 characters
- * @param code The content of the code block.
- * @param length The maximum length of the code block.
- * @param language The language of the code.
- * @param substr Whether or not to substring the code if it is too long.
- * @returns The generated code block
- */
- public async codeblock(code: string, length: number, language: CodeBlockLang | '' = '', substr = false): Promise<string> {
- let hasteOut = '';
- code = escapeCodeBlock(code);
- const prefix = `\`\`\`${language}\n`;
- const suffix = '\n```';
- if (code.length + (prefix + suffix).length >= length) {
- const haste_ = await this.haste(code, substr);
- hasteOut = `Too large to display. ${
- haste_.url
- ? `Hastebin: ${haste_.url}${language ? `.${language}` : ''}${haste_.error ? ` - ${haste_.error}` : ''}`
- : `${emojis.error} Hastebin: ${haste_.error}`
- }`;
- }
-
- const FormattedHaste = hasteOut.length ? `\n${hasteOut}` : '';
- const shortenedCode = hasteOut ? code.substring(0, length - (prefix + FormattedHaste + suffix).length) : code;
- const code3 = code.length ? prefix + shortenedCode + suffix + FormattedHaste : prefix + suffix;
- if (code3.length > length) {
- void this.client.console.warn(`codeblockError`, `Required Length: ${length}. Actual Length: ${code3.length}`, true);
- void this.client.console.warn(`codeblockError`, code3, true);
- throw new Error('code too long');
- }
- return code3;
- }
-
- /**
- * Maps the key of a credential with a readable version when redacting.
- * @param key The key of the credential.
- * @returns The readable version of the key or the original key if there isn't a mapping.
- */
- #mapCredential(key: string): string {
- return (
- {
- token: 'Main Token',
- devToken: 'Dev Token',
- betaToken: 'Beta Token',
- hypixelApiKey: 'Hypixel Api Key',
- wolframAlphaAppId: 'Wolfram|Alpha App ID',
- dbPassword: 'Database Password'
- }[key] ?? key
- );
- }
-
- /**
- * Redacts credentials from a string.
- * @param text The text to redact credentials from.
- * @returns The redacted text.
- */
- public redact(text: string) {
- for (const credentialName in { ...this.client.config.credentials, dbPassword: this.client.config.db.password }) {
- const credential = { ...this.client.config.credentials, dbPassword: this.client.config.db.password }[
- credentialName as keyof typeof this.client.config.credentials
- ];
- if (credential === null || credential === '') continue;
- const replacement = this.#mapCredential(credentialName);
- const escapeRegex = /[.*+?^${}()|[\]\\]/g;
- text = text.replace(new RegExp(credential.toString().replace(escapeRegex, '\\$&'), 'g'), `[${replacement} Omitted]`);
- text = text.replace(
- new RegExp([...credential.toString()].reverse().join('').replace(escapeRegex, '\\$&'), 'g'),
- `[${replacement} Omitted]`
- );
- }
- return text;
- }
-
- /**
- * Takes an any value, inspects it, redacts credentials, and puts it in a codeblock
- * (and uploads to hast if the content is too long).
- * @param input The object to be inspect, redacted, and put into a codeblock.
- * @param language The language to make the codeblock.
- * @param inspectOptions The options for {@link BushClientUtil.inspect}.
- * @param length The maximum length that the codeblock can be.
- * @returns The generated codeblock.
- */
- public async inspectCleanRedactCodeblock(
- input: any,
- language?: CodeBlockLang | '',
- inspectOptions?: BushInspectOptions,
- length = 1024
- ) {
- input = inspect(input, inspectOptions ?? undefined);
- if (inspectOptions) inspectOptions.inspectStrings = undefined;
- input = cleanCodeBlockContent(input);
- input = this.redact(input);
- return this.codeblock(input, length, language, true);
- }
-
- /**
- * Takes an any value, inspects it, redacts credentials, and uploads it to haste.
- * @param input The object to be inspect, redacted, and upload.
- * @param inspectOptions The options for {@link BushClientUtil.inspect}.
- * @returns The {@link HasteResults}.
- */
- public async inspectCleanRedactHaste(input: any, inspectOptions?: BushInspectOptions): Promise<HasteResults> {
- input = inspect(input, inspectOptions ?? undefined);
- input = this.redact(input);
- return this.haste(input, true);
- }
-
- /**
- * Takes an any value, inspects it and redacts credentials.
- * @param input The object to be inspect and redacted.
- * @param inspectOptions The options for {@link BushClientUtil.inspect}.
- * @returns The redacted and inspected object.
- */
- public inspectAndRedact(input: any, inspectOptions?: BushInspectOptions): string {
- input = inspect(input, inspectOptions ?? undefined);
- return this.redact(input);
- }
-
- /**
- * Get the global cache.
- */
- public getGlobal(): GlobalCache;
- /**
- * Get a key from the global cache.
- * @param key The key to get in the global cache.
- */
- public getGlobal<K extends keyof GlobalCache>(key: K): GlobalCache[K];
- public getGlobal(key?: keyof GlobalCache) {
- return key ? this.client.cache.global[key] : this.client.cache.global;
- }
-
- /**
- * Get the shared cache.
- */
- public getShared(): SharedCache;
- /**
- * Get a key from the shared cache.
- * @param key The key to get in the shared cache.
- */
- public getShared<K extends keyof SharedCache>(key: K): SharedCache[K];
- public getShared(key?: keyof SharedCache) {
- return key ? this.client.cache.shared[key] : this.client.cache.shared;
- }
-
- /**
- * Add or remove an element from an array stored in the Globals database.
- * @param action Either `add` or `remove` an element.
- * @param key The key of the element in the global cache to update.
- * @param value The value to add/remove from the array.
- */
- public async insertOrRemoveFromGlobal<K extends keyof Client['cache']['global']>(
- action: 'add' | 'remove',
- key: K,
- value: Client['cache']['global'][K][0]
- ): Promise<Global | void> {
- const row =
- (await Global.findByPk(this.client.config.environment)) ??
- (await Global.create({ environment: this.client.config.environment }));
- const oldValue: any[] = row[key];
- const newValue = addOrRemoveFromArray(action, oldValue, value);
- row[key] = newValue;
- this.client.cache.global[key] = newValue;
- return await row.save().catch((e) => this.handleError('insertOrRemoveFromGlobal', e));
- }
-
- /**
- * Add or remove an element from an array stored in the Shared database.
- * @param action Either `add` or `remove` an element.
- * @param key The key of the element in the shared cache to update.
- * @param value The value to add/remove from the array.
- */
- public async insertOrRemoveFromShared<K extends Exclude<keyof Client['cache']['shared'], 'badWords' | 'autoBanCode'>>(
- action: 'add' | 'remove',
- key: K,
- value: Client['cache']['shared'][K][0]
- ): Promise<Shared | void> {
- const row = (await Shared.findByPk(0)) ?? (await Shared.create());
- const oldValue: any[] = row[key];
- const newValue = addOrRemoveFromArray(action, oldValue, value);
- row[key] = newValue;
- this.client.cache.shared[key] = newValue;
- return await row.save().catch((e) => this.handleError('insertOrRemoveFromShared', e));
- }
-
- /**
- * Updates an element in the Globals database.
- * @param key The key in the global cache to update.
- * @param value The value to set the key to.
- */
- public async setGlobal<K extends keyof Client['cache']['global']>(
- key: K,
- value: Client['cache']['global'][K]
- ): Promise<Global | void> {
- const row =
- (await Global.findByPk(this.client.config.environment)) ??
- (await Global.create({ environment: this.client.config.environment }));
- row[key] = value;
- this.client.cache.global[key] = value;
- return await row.save().catch((e) => this.handleError('setGlobal', e));
- }
-
- /**
- * Updates an element in the Shared database.
- * @param key The key in the shared cache to update.
- * @param value The value to set the key to.
- */
- public async setShared<K extends Exclude<keyof Client['cache']['shared'], 'badWords' | 'autoBanCode'>>(
- key: K,
- value: Client['cache']['shared'][K]
- ): Promise<Shared | void> {
- const row = (await Shared.findByPk(0)) ?? (await Shared.create());
- row[key] = value;
- this.client.cache.shared[key] = value;
- return await row.save().catch((e) => this.handleError('setShared', e));
- }
-
- /**
- * Send a message in the error logging channel and console for an error.
- * @param context
- * @param error
- */
- public async handleError(context: string, error: Error) {
- await this.client.console.error(_.camelCase(context), `An error occurred:\n${formatError(error, false)}`, false);
- await this.client.console.channelError({
- embeds: await CommandErrorListener.generateErrorEmbed(this.client, { type: 'unhandledRejection', error: error, context })
- });
- }
-
- /**
- * Fetches a user from discord.
- * @param user The user to fetch
- * @returns Undefined if the user is not found, otherwise the user.
- */
- public async resolveNonCachedUser(user: UserResolvable | undefined | null): Promise<User | undefined> {
- if (user == null) return undefined;
- const resolvedUser =
- user instanceof User
- ? user
- : user instanceof GuildMember
- ? user.user
- : user instanceof ThreadMember
- ? user.user
- : user instanceof Message
- ? user.author
- : undefined;
-
- return resolvedUser ?? (await this.client.users.fetch(user as Snowflake).catch(() => undefined));
- }
-
- /**
- * Get the pronouns of a discord user from pronoundb.org
- * @param user The user to retrieve the promises of.
- * @returns The human readable pronouns of the user, or undefined if they do not have any.
- */
- public async getPronounsOf(user: User | Snowflake): Promise<Pronoun | undefined> {
- 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()
- .catch(() => undefined)) as { pronouns: PronounCode } | undefined;
-
- if (!apiRes) return undefined;
- assert(apiRes.pronouns);
-
- return pronounMapping[apiRes.pronouns!]!;
- }
-
- /**
- * Uploads an image to imgur.
- * @param image The image to upload.
- * @returns The url of the imgur.
- */
- 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()) as { data: { link: string } };
-
- return resp.data.link;
- }
-
- /**
- * Gets the prefix based off of the message.
- * @param message The message to get the prefix from.
- * @returns The prefix.
- */
- public prefix(message: CommandMessage | SlashMessage): string {
- return message.util.isSlash
- ? '/'
- : this.client.config.isDevelopment
- ? 'dev '
- : message.util.parsed?.prefix ?? this.client.config.prefix;
- }
-
- public async resolveMessageLinks(content: string | null): Promise<MessageLinkParts[]> {
- const res: MessageLinkParts[] = [];
-
- if (!content) return res;
-
- const regex_ = new RegExp(regex.messageLink);
- let match: RegExpExecArray | null;
- while (((match = regex_.exec(content)), match !== null)) {
- const input = match.input;
- if (!match.groups || !input) continue;
- if (input.startsWith('<') && input.endsWith('>')) continue;
-
- const { guild_id, channel_id, message_id } = match.groups;
- if (!guild_id || !channel_id || !message_id) continue;
-
- res.push({ guild_id, channel_id, message_id });
- }
-
- return res;
- }
-
- public async resolveMessagesFromLinks(content: string): Promise<APIMessage[]> {
- const res: APIMessage[] = [];
-
- const links = await this.resolveMessageLinks(content);
- if (!links.length) return [];
-
- for (const { guild_id, channel_id, message_id } of links) {
- const guild = this.client.guilds.cache.get(guild_id);
- if (!guild) continue;
- const channel = guild.channels.cache.get(channel_id);
- if (!channel || (!channel.isTextBased() && !channel.isThread())) continue;
-
- const message = (await this.client.rest
- .get(Routes.channelMessage(channel_id, message_id))
- .catch(() => null)) as APIMessage | null;
- if (!message) continue;
-
- res.push(message);
- }
-
- return res;
- }
-
- /**
- * Resolves a channel from the config and ensures it is a non-dm-based-text-channel.
- * @param channel The channel to retrieve.
- */
- public async getConfigChannel(
- channel: ConfigChannelKey
- ): Promise<Exclude<TextBasedChannel, DMChannel | PartialDMChannel> | null> {
- const channels = this.client.config.channels;
- if (!(channel in channels))
- throw new TypeError(`Invalid channel provided (${channel}), must be one of ${Object.keys(channels).join(' ')}`);
-
- const channelId = channels[channel];
- if (channelId === '') return null;
-
- const res = await this.client.channels.fetch(channelId);
-
- if (!res?.isTextBased() || res.isDMBased()) return null;
-
- return res;
- }
-}
-
-interface HastebinRes {
- key: string;
-}
-
-export interface HasteResults {
- url?: string;
- error?: 'content too long' | 'substr' | 'unable to post';
-}
-
-export interface MessageLinkParts {
- guild_id: Snowflake;
- channel_id: Snowflake;
- message_id: Snowflake;
-}
diff --git a/src/lib/utils/BushConstants.ts b/src/lib/utils/BushConstants.ts
deleted file mode 100644
index 090616c..0000000
--- a/src/lib/utils/BushConstants.ts
+++ /dev/null
@@ -1,531 +0,0 @@
-import deepLock from 'deep-lock';
-import {
- ArgumentMatches as AkairoArgumentMatches,
- ArgumentTypes as AkairoArgumentTypes,
- BuiltInReasons,
- CommandHandlerEvents as AkairoCommandHandlerEvents
-} from 'discord-akairo/dist/src/util/Constants.js';
-import { Colors, GuildFeature } from 'discord.js';
-
-const rawCapeUrl = 'https://raw.githubusercontent.com/NotEnoughUpdates/capes/master/';
-
-/**
- * Time units in milliseconds
- */
-export const enum Time {
- /**
- * One millisecond (1 ms).
- */
- Millisecond = 1,
-
- /**
- * One second (1,000 ms).
- */
- Second = Millisecond * 1000,
-
- /**
- * One minute (60,000 ms).
- */
- Minute = Second * 60,
-
- /**
- * One hour (3,600,000 ms).
- */
- Hour = Minute * 60,
-
- /**
- * One day (86,400,000 ms).
- */
- Day = Hour * 24,
-
- /**
- * One week (604,800,000 ms).
- */
- Week = Day * 7,
-
- /**
- * One month (2,629,800,000 ms).
- */
- Month = Day * 30.4375, // average of days in a month (including leap years)
-
- /**
- * One year (31,557,600,000 ms).
- */
- Year = Day * 365.25 // average with leap years
-}
-
-export const emojis = Object.freeze({
- success: '<:success:837109864101707807>',
- warn: '<:warn:848726900876247050>',
- error: '<:error:837123021016924261>',
- successFull: '<:success_full:850118767576088646>',
- warnFull: '<:warn_full:850118767391539312>',
- errorFull: '<:error_full:850118767295201350>',
- mad: '<:mad:783046135392239626>',
- join: '<:join:850198029809614858>',
- leave: '<:leave:850198048205307919>',
- loading: '<a:Loading:853419254619963392>',
- offlineCircle: '<:offline:787550565382750239>',
- dndCircle: '<:dnd:787550487633330176>',
- idleCircle: '<:idle:787550520956551218>',
- onlineCircle: '<:online:787550449435803658>',
- cross: '<:cross:878319362539421777>',
- check: '<:check:878320135297961995>'
-} as const);
-
-export const emojisRaw = Object.freeze({
- success: '837109864101707807',
- warn: '848726900876247050',
- error: '837123021016924261',
- successFull: '850118767576088646',
- warnFull: '850118767391539312',
- errorFull: '850118767295201350',
- mad: '783046135392239626',
- join: '850198029809614858',
- leave: '850198048205307919',
- loading: '853419254619963392',
- offlineCircle: '787550565382750239',
- dndCircle: '787550487633330176',
- idleCircle: '787550520956551218',
- onlineCircle: '787550449435803658',
- cross: '878319362539421777',
- check: '878320135297961995'
-} as const);
-
-export const colors = Object.freeze({
- default: 0x1fd8f1,
- error: 0xef4947,
- warn: 0xfeba12,
- success: 0x3bb681,
- info: 0x3b78ff,
- red: 0xff0000,
- blue: 0x0055ff,
- aqua: 0x00bbff,
- purple: 0x8400ff,
- blurple: 0x5440cd,
- newBlurple: 0x5865f2,
- pink: 0xff00e6,
- green: 0x00ff1e,
- darkGreen: 0x008f11,
- gold: 0xb59400,
- yellow: 0xffff00,
- white: 0xffffff,
- gray: 0xa6a6a6,
- lightGray: 0xcfcfcf,
- darkGray: 0x7a7a7a,
- black: 0x000000,
- orange: 0xe86100,
- ...Colors
-} as const);
-
-// Somewhat stolen from @Mzato0001
-export const timeUnits = deepLock({
- milliseconds: {
- match: / (?:(?<milliseconds>-?(?:\d+)?\.?\d+) *(?:milliseconds?|msecs?|ms))/im,
- value: Time.Millisecond
- },
- seconds: {
- match: / (?:(?<seconds>-?(?:\d+)?\.?\d+) *(?:seconds?|secs?|s))/im,
- value: Time.Second
- },
- minutes: {
- match: / (?:(?<minutes>-?(?:\d+)?\.?\d+) *(?:minutes?|mins?|m))/im,
- value: Time.Minute
- },
- hours: {
- match: / (?:(?<hours>-?(?:\d+)?\.?\d+) *(?:hours?|hrs?|h))/im,
- value: Time.Hour
- },
- days: {
- match: / (?:(?<days>-?(?:\d+)?\.?\d+) *(?:days?|d))/im,
- value: Time.Day
- },
- weeks: {
- match: / (?:(?<weeks>-?(?:\d+)?\.?\d+) *(?:weeks?|w))/im,
- value: Time.Week
- },
- months: {
- match: / (?:(?<months>-?(?:\d+)?\.?\d+) *(?:months?|mon|mo))/im,
- value: Time.Month
- },
- years: {
- match: / (?:(?<years>-?(?:\d+)?\.?\d+) *(?:years?|y))/im,
- value: Time.Year
- }
-} as const);
-
-export const regex = deepLock({
- snowflake: /^\d{15,21}$/im,
-
- discordEmoji: /<a?:(?<name>[a-zA-Z0-9_]+):(?<id>\d{15,21})>/im,
-
- /*
- * Taken with permission from Geek:
- * https://github.com/FireDiscordBot/bot/blob/5d1990e5f8b52fcc72261d786aa3c7c7c65ab5e8/lib/util/constants.ts#L276
- */
- /** **This has the global flag, make sure to handle it correctly.** */
- messageLink:
- /<?https:\/\/(?:ptb\.|canary\.|staging\.)?discord(?:app)?\.com?\/channels\/(?<guild_id>\d{15,21})\/(?<channel_id>\d{15,21})\/(?<message_id>\d{15,21})>?/gim
-} as const);
-
-/**
- * Maps the response from pronoundb.org to a readable format
- */
-export const pronounMapping = Object.freeze({
- unspecified: 'Unspecified',
- hh: 'He/Him',
- hi: 'He/It',
- hs: 'He/She',
- ht: 'He/They',
- ih: 'It/Him',
- ii: 'It/Its',
- is: 'It/She',
- it: 'It/They',
- shh: 'She/He',
- sh: 'She/Her',
- si: 'She/It',
- st: 'She/They',
- th: 'They/He',
- ti: 'They/It',
- ts: 'They/She',
- tt: 'They/Them',
- any: 'Any pronouns',
- other: 'Other pronouns',
- ask: 'Ask me my pronouns',
- avoid: 'Avoid pronouns, use my name'
-} as const);
-
-/**
- * A bunch of mappings
- */
-export const mappings = deepLock({
- guilds: {
- "Moulberry's Bush": '516977525906341928',
- "Moulberry's Tree": '767448775450820639',
- 'MB Staff': '784597260465995796',
- "IRONM00N's Space Ship": '717176538717749358'
- },
-
- channels: {
- 'neu-support': '714332750156660756',
- 'giveaways': '767782084981817344'
- },
-
- users: {
- IRONM00N: '322862723090219008',
- Moulberry: '211288288055525376',
- nopo: '384620942577369088',
- Bestower: '496409778822709251'
- },
-
- permissions: {
- CreateInstantInvite: { name: 'Create Invite', important: false },
- KickMembers: { name: 'Kick Members', important: true },
- BanMembers: { name: 'Ban Members', important: true },
- Administrator: { name: 'Administrator', important: true },
- ManageChannels: { name: 'Manage Channels', important: true },
- ManageGuild: { name: 'Manage Server', important: true },
- AddReactions: { name: 'Add Reactions', important: false },
- ViewAuditLog: { name: 'View Audit Log', important: true },
- PrioritySpeaker: { name: 'Priority Speaker', important: true },
- Stream: { name: 'Video', important: false },
- ViewChannel: { name: 'View Channel', important: false },
- SendMessages: { name: 'Send Messages', important: false },
- SendTTSMessages: { name: 'Send Text-to-Speech Messages', important: true },
- ManageMessages: { name: 'Manage Messages', important: true },
- EmbedLinks: { name: 'Embed Links', important: false },
- AttachFiles: { name: 'Attach Files', important: false },
- ReadMessageHistory: { name: 'Read Message History', important: false },
- MentionEveryone: { name: 'Mention @\u200Beveryone, @\u200Bhere, and All Roles', important: true }, // name has a zero-width space to prevent accidents
- UseExternalEmojis: { name: 'Use External Emoji', important: false },
- ViewGuildInsights: { name: 'View Server Insights', important: true },
- Connect: { name: 'Connect', important: false },
- Speak: { name: 'Speak', important: false },
- MuteMembers: { name: 'Mute Members', important: true },
- DeafenMembers: { name: 'Deafen Members', important: true },
- MoveMembers: { name: 'Move Members', important: true },
- UseVAD: { name: 'Use Voice Activity', important: false },
- ChangeNickname: { name: 'Change Nickname', important: false },
- ManageNicknames: { name: 'Change Nicknames', important: true },
- ManageRoles: { name: 'Manage Roles', important: true },
- ManageWebhooks: { name: 'Manage Webhooks', important: true },
- ManageEmojisAndStickers: { name: 'Manage Emojis and Stickers', important: true },
- UseApplicationCommands: { name: 'Use Slash Commands', important: false },
- RequestToSpeak: { name: 'Request to Speak', important: false },
- ManageEvents: { name: 'Manage Events', important: true },
- ManageThreads: { name: 'Manage Threads', important: true },
- CreatePublicThreads: { name: 'Create Public Threads', important: false },
- CreatePrivateThreads: { name: 'Create Private Threads', important: false },
- UseExternalStickers: { name: 'Use External Stickers', important: false },
- SendMessagesInThreads: { name: 'Send Messages In Threads', important: false },
- StartEmbeddedActivities: { name: 'Start Activities', important: false },
- ModerateMembers: { name: 'Timeout Members', important: true },
- UseEmbeddedActivities: { name: 'Use Activities', important: false }
- },
-
- // prettier-ignore
- features: {
- [GuildFeature.Verified]: { name: 'Verified', important: true, emoji: '<:verified:850795049817473066>', weight: 0 },
- [GuildFeature.Partnered]: { name: 'Partnered', important: true, emoji: '<:partneredServer:850794851955507240>', weight: 1 },
- [GuildFeature.MoreStickers]: { name: 'More Stickers', important: true, emoji: null, weight: 2 },
- MORE_EMOJIS: { name: 'More Emoji', important: true, emoji: '<:moreEmoji:850786853497602080>', weight: 3 },
- [GuildFeature.Featurable]: { name: 'Featurable', important: true, emoji: '<:featurable:850786776372084756>', weight: 4 },
- [GuildFeature.RelayEnabled]: { name: 'Relay Enabled', important: true, emoji: '<:relayEnabled:850790531441229834>', weight: 5 },
- [GuildFeature.Discoverable]: { name: 'Discoverable', important: true, emoji: '<:discoverable:850786735360966656>', weight: 6 },
- ENABLED_DISCOVERABLE_BEFORE: { name: 'Enabled Discovery Before', important: false, emoji: '<:enabledDiscoverableBefore:850786754670624828>', weight: 7 },
- [GuildFeature.MonetizationEnabled]: { name: 'Monetization Enabled', important: true, emoji: null, weight: 8 },
- [GuildFeature.TicketedEventsEnabled]: { name: 'Ticketed Events Enabled', important: true, emoji: null, weight: 9 },
- [GuildFeature.PreviewEnabled]: { name: 'Preview Enabled', important: true, emoji: '<:previewEnabled:850790508266913823>', weight: 10 },
- COMMERCE: { name: 'Store Channels', important: true, emoji: '<:storeChannels:850786692432396338>', weight: 11 },
- [GuildFeature.VanityURL]: { name: 'Vanity URL', important: false, emoji: '<:vanityURL:850790553079644160>', weight: 12 },
- [GuildFeature.VIPRegions]: { name: 'VIP Regions', important: false, emoji: '<:VIPRegions:850794697496854538>', weight: 13 },
- [GuildFeature.AnimatedIcon]: { name: 'Animated Icon', important: false, emoji: '<:animatedIcon:850774498071412746>', weight: 14 },
- [GuildFeature.Banner]: { name: 'Banner', important: false, emoji: '<:banner:850786673150787614>', weight: 15 },
- [GuildFeature.InviteSplash]: { name: 'Invite Splash', important: false, emoji: '<:inviteSplash:850786798246559754>', weight: 16 },
- [GuildFeature.PrivateThreads]: { name: 'Private Threads', important: false, emoji: '<:privateThreads:869763711894700093>', weight: 17 },
- THREE_DAY_THREAD_ARCHIVE: { name: 'Three Day Thread Archive', important: false, emoji: '<:threeDayThreadArchive:869767841652564008>', weight: 19 },
- SEVEN_DAY_THREAD_ARCHIVE: { name: 'Seven Day Thread Archive', important: false, emoji: '<:sevenDayThreadArchive:869767896123998288>', weight: 20 },
- [GuildFeature.RoleIcons]: { name: 'Role Icons', important: false, emoji: '<:roleIcons:876993381929222175>', weight: 21 },
- [GuildFeature.News]: { name: 'Announcement Channels', important: false, emoji: '<:announcementChannels:850790491796013067>', weight: 22 },
- [GuildFeature.MemberVerificationGateEnabled]: { name: 'Membership Verification Gate', important: false, emoji: '<:memberVerificationGateEnabled:850786829984858212>', weight: 23 },
- [GuildFeature.WelcomeScreenEnabled]: { name: 'Welcome Screen Enabled', important: false, emoji: '<:welcomeScreenEnabled:850790575875817504>', weight: 24 },
- [GuildFeature.Community]: { name: 'Community', important: false, emoji: '<:community:850786714271875094>', weight: 25 },
- THREADS_ENABLED: {name: 'Threads Enabled', important: false, emoji: '<:threadsEnabled:869756035345317919>', weight: 26 },
- THREADS_ENABLED_TESTING: {name: 'Threads Enabled Testing', important: false, emoji: null, weight: 27 },
- [GuildFeature.AnimatedBanner]: { name: 'Animated Banner', important: false, emoji: null, weight: 28 },
- [GuildFeature.HasDirectoryEntry]: { name: 'Has Directory Entry', important: true, emoji: null, weight: 29 },
- [GuildFeature.Hub]: { name: 'Hub', important: true, emoji: null, weight: 30 },
- [GuildFeature.LinkedToHub]: { name: 'Linked To Hub', important: true, emoji: null, weight: 31 },
- },
-
- regions: {
- 'automatic': ':united_nations: Automatic',
- 'brazil': ':flag_br: Brazil',
- 'europe': ':flag_eu: Europe',
- 'hongkong': ':flag_hk: Hongkong',
- 'india': ':flag_in: India',
- 'japan': ':flag_jp: Japan',
- 'russia': ':flag_ru: Russia',
- 'singapore': ':flag_sg: Singapore',
- 'southafrica': ':flag_za: South Africa',
- 'sydney': ':flag_au: Sydney',
- 'us-central': ':flag_us: US Central',
- 'us-east': ':flag_us: US East',
- 'us-south': ':flag_us: US South',
- 'us-west': ':flag_us: US West'
- },
-
- otherEmojis: {
- ServerBooster1: '<:serverBooster1:848740052091142145>',
- ServerBooster2: '<:serverBooster2:848740090506510388>',
- ServerBooster3: '<:serverBooster3:848740124992077835>',
- ServerBooster6: '<:serverBooster6:848740155245461514>',
- ServerBooster9: '<:serverBooster9:848740188846030889>',
- ServerBooster12: '<:serverBooster12:848740304365551668>',
- ServerBooster15: '<:serverBooster15:848740354890137680>',
- ServerBooster18: '<:serverBooster18:848740402886606868>',
- ServerBooster24: '<:serverBooster24:848740444628320256>',
- Nitro: '<:nitro:848740498054971432>',
- Booster: '<:booster:848747775020892200>',
- Owner: '<:owner:848746439311753286>',
- Admin: '<:admin:848963914628333598>',
- Superuser: '<:superUser:848947986326224926>',
- Developer: '<:developer:848954538111139871>',
- Bot: '<:bot:1006929813203853427>',
- BushVerified: '<:verfied:853360152090771497>',
- BoostTier1: '<:boostitle:853363736679940127>',
- BoostTier2: '<:boostitle:853363752728789075>',
- BoostTier3: '<:boostitle:853363769132056627>',
- ChannelText: '<:text:853375537791893524>',
- ChannelNews: '<:announcements:853375553531674644>',
- ChannelVoice: '<:voice:853375566735212584>',
- ChannelStage: '<:stage:853375583521210468>',
- // ChannelStore: '<:store:853375601175691266>',
- ChannelCategory: '<:category:853375615260819476>',
- ChannelThread: '<:thread:865033845753249813>'
- },
-
- userFlags: {
- Staff: '<:discordEmployee:848742947826434079>',
- Partner: '<:partneredServerOwner:848743051593777152>',
- Hypesquad: '<:hypeSquadEvents:848743108283072553>',
- BugHunterLevel1: '<:bugHunter:848743239850393640>',
- HypeSquadOnlineHouse1: '<:hypeSquadBravery:848742910563844127>',
- HypeSquadOnlineHouse2: '<:hypeSquadBrilliance:848742840649646101>',
- HypeSquadOnlineHouse3: '<:hypeSquadBalance:848742877537370133>',
- PremiumEarlySupporter: '<:earlySupporter:848741030102171648>',
- TeamPseudoUser: 'TeamPseudoUser',
- BugHunterLevel2: '<:bugHunterGold:848743283080822794>',
- VerifiedBot: '<:verifiedbot_rebrand1:938928232667947028><:verifiedbot_rebrand2:938928355707879475>',
- VerifiedDeveloper: '<:earlyVerifiedBotDeveloper:848741079875846174>',
- CertifiedModerator: '<:discordCertifiedModerator:877224285901582366>',
- BotHTTPInteractions: 'BotHTTPInteractions',
- Spammer: 'Spammer',
- Quarantined: 'Quarantined'
- },
-
- status: {
- online: '<:online:848937141639577690>',
- idle: '<:idle:848937158261211146>',
- dnd: '<:dnd:848937173780135986>',
- offline: '<:offline:848939387277672448>',
- streaming: '<:streaming:848937187479519242>'
- },
-
- maybeNitroDiscrims: ['1111', '2222', '3333', '4444', '5555', '6666', '6969', '7777', '8888', '9999'],
-
- capes: [
- /* supporter capes */
- { name: 'patreon1', purchasable: false /* moulberry no longer offers */ },
- { name: 'patreon2', purchasable: false /* moulberry no longer offers */ },
- { name: 'fade', custom: `${rawCapeUrl}fade.gif`, purchasable: true },
- { name: 'lava', custom: `${rawCapeUrl}lava.gif`, purchasable: true },
- { name: 'mcworld', custom: `${rawCapeUrl}mcworld_compressed.gif`, purchasable: true },
- { name: 'negative', custom: `${rawCapeUrl}negative_compressed.gif`, purchasable: true },
- { name: 'space', custom: `${rawCapeUrl}space_compressed.gif`, purchasable: true },
- { name: 'void', custom: `${rawCapeUrl}void.gif`, purchasable: true },
- { name: 'tunnel', custom: `${rawCapeUrl}tunnel.gif`, purchasable: true },
- /* Staff capes */
- { name: 'contrib' },
- { name: 'mbstaff' },
- { name: 'ironmoon' },
- { name: 'gravy' },
- { name: 'nullzee' },
- /* partner capes */
- { name: 'thebakery' },
- { name: 'dsm' },
- { name: 'packshq' },
- { name: 'furf' },
- { name: 'skytils' },
- { name: 'sbp' },
- { name: 'subreddit_light' },
- { name: 'subreddit_dark' },
- { name: 'skyclient' },
- { name: 'sharex' },
- { name: 'sharex_white' },
- /* streamer capes */
- { name: 'alexxoffi' },
- { name: 'jakethybro' },
- { name: 'krusty' },
- { name: 'krusty_day' },
- { name: 'krusty_night' },
- { name: 'krusty_sunset' },
- { name: 'soldier' },
- { name: 'zera' },
- { name: 'secondpfirsisch' },
- { name: 'stormy_lh' }
- ].map((value, index) => ({ ...value, index })),
-
- roleMap: [
- { name: '*', id: '792453550768390194' },
- { name: 'Admin Perms', id: '746541309853958186' },
- { name: 'Sr. Moderator', id: '782803470205190164' },
- { name: 'Moderator', id: '737308259823910992' },
- { name: 'Helper', id: '737440116230062091' },
- { name: 'Trial Helper', id: '783537091946479636' },
- { name: 'Contributor', id: '694431057532944425' },
- { name: 'Giveaway Donor', id: '784212110263451649' },
- { name: 'Giveaway (200m)', id: '810267756426690601' },
- { name: 'Giveaway (100m)', id: '801444430522613802' },
- { name: 'Giveaway (50m)', id: '787497512981757982' },
- { name: 'Giveaway (25m)', id: '787497515771232267' },
- { name: 'Giveaway (10m)', id: '787497518241153025' },
- { name: 'Giveaway (5m)', id: '787497519768403989' },
- { name: 'Giveaway (1m)', id: '787497521084891166' },
- { name: 'Suggester', id: '811922322767609877' },
- { name: 'Partner', id: '767324547312779274' },
- { name: 'Level Locked', id: '784248899044769792' },
- { name: 'No Files', id: '786421005039173633' },
- { name: 'No Reactions', id: '786421270924361789' },
- { name: 'No Links', id: '786421269356740658' },
- { name: 'No Bots', id: '786804858765312030' },
- { name: 'No VC', id: '788850482554208267' },
- { name: 'No Giveaways', id: '808265422334984203' },
- { name: 'No Support', id: '790247359824396319' }
- ],
-
- roleWhitelist: {
- 'Partner': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Suggester': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator', 'Helper', 'Trial Helper', 'Contributor'],
- 'Level Locked': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No Files': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No Reactions': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No Links': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No Bots': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No VC': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No Giveaways': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator', 'Helper'],
- 'No Support': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway Donor': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (200m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (100m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (50m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (25m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (10m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (5m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (1m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator']
- }
-} as const);
-
-export const ArgumentMatches = Object.freeze({
- ...AkairoArgumentMatches
-} as const);
-
-export const ArgumentTypes = Object.freeze({
- ...AkairoArgumentTypes,
- DURATION: 'duration',
- CONTENT_WITH_DURATION: 'contentWithDuration',
- PERMISSION: 'permission',
- SNOWFLAKE: 'snowflake',
- DISCORD_EMOJI: 'discordEmoji',
- ROLE_WITH_DURATION: 'roleWithDuration',
- ABBREVIATED_NUMBER: 'abbreviatedNumber',
- GLOBAL_USER: 'globalUser'
-} as const);
-
-export const BlockedReasons = Object.freeze({
- ...BuiltInReasons,
- DISABLED_GUILD: 'disabledGuild',
- DISABLED_GLOBAL: 'disabledGlobal',
- ROLE_BLACKLIST: 'roleBlacklist',
- USER_GUILD_BLACKLIST: 'userGuildBlacklist',
- USER_GLOBAL_BLACKLIST: 'userGlobalBlacklist',
- RESTRICTED_GUILD: 'restrictedGuild',
- CHANNEL_GUILD_BLACKLIST: 'channelGuildBlacklist',
- CHANNEL_GLOBAL_BLACKLIST: 'channelGlobalBlacklist',
- RESTRICTED_CHANNEL: 'restrictedChannel'
-} as const);
-
-export const CommandHandlerEvents = Object.freeze({
- ...AkairoCommandHandlerEvents
-} as const);
-
-export const moulberryBushRoleMap = deepLock([
- { name: '*', id: '792453550768390194' },
- { name: 'Admin Perms', id: '746541309853958186' },
- { name: 'Sr. Moderator', id: '782803470205190164' },
- { name: 'Moderator', id: '737308259823910992' },
- { name: 'Helper', id: '737440116230062091' },
- { name: 'Trial Helper', id: '783537091946479636' },
- { name: 'Contributor', id: '694431057532944425' },
- { name: 'Giveaway Donor', id: '784212110263451649' },
- { name: 'Giveaway (200m)', id: '810267756426690601' },
- { name: 'Giveaway (100m)', id: '801444430522613802' },
- { name: 'Giveaway (50m)', id: '787497512981757982' },
- { name: 'Giveaway (25m)', id: '787497515771232267' },
- { name: 'Giveaway (10m)', id: '787497518241153025' },
- { name: 'Giveaway (5m)', id: '787497519768403989' },
- { name: 'Giveaway (1m)', id: '787497521084891166' },
- { name: 'Suggester', id: '811922322767609877' },
- { name: 'Partner', id: '767324547312779274' },
- { name: 'Level Locked', id: '784248899044769792' },
- { name: 'No Files', id: '786421005039173633' },
- { name: 'No Reactions', id: '786421270924361789' },
- { name: 'No Links', id: '786421269356740658' },
- { name: 'No Bots', id: '786804858765312030' },
- { name: 'No VC', id: '788850482554208267' },
- { name: 'No Giveaways', id: '808265422334984203' },
- { name: 'No Support', id: '790247359824396319' }
-] as const);
-
-export type PronounCode = keyof typeof pronounMapping;
-export type Pronoun = typeof pronounMapping[PronounCode];
diff --git a/src/lib/utils/BushLogger.ts b/src/lib/utils/BushLogger.ts
deleted file mode 100644
index 4acda69..0000000
--- a/src/lib/utils/BushLogger.ts
+++ /dev/null
@@ -1,315 +0,0 @@
-import chalk from 'chalk';
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-import { bold, Client, EmbedBuilder, escapeMarkdown, PartialTextBasedChannelFields, type Message } from 'discord.js';
-import { stripVTControlCharacters as stripColor } from 'node:util';
-import repl, { REPLServer, REPL_MODE_STRICT } from 'repl';
-import { WriteStream } from 'tty';
-import { type SendMessageType } from '../extensions/discord-akairo/BushClient.js';
-import { colors } from './BushConstants.js';
-import { inspect } from './BushUtils.js';
-
-let REPL: REPLServer;
-let replGone = false;
-
-export function init() {
- const kFormatForStdout = Object.getOwnPropertySymbols(console).find((sym) => sym.toString() === 'Symbol(kFormatForStdout)')!;
- const kFormatForStderr = Object.getOwnPropertySymbols(console).find((sym) => sym.toString() === 'Symbol(kFormatForStderr)')!;
-
- REPL = repl.start({
- useColors: true,
- terminal: true,
- useGlobal: true,
- replMode: REPL_MODE_STRICT,
- breakEvalOnSigint: true,
- ignoreUndefined: true
- });
-
- const apply = (stream: WriteStream, symbol: symbol): ProxyHandler<typeof console['log']>['apply'] =>
- function apply(target, thisArg, args) {
- if (stream.isTTY) {
- stream.moveCursor(0, -1);
- stream.write('\n');
- stream.clearLine(0);
- }
-
- const ret = target(...args);
-
- if (stream.isTTY) {
- const formatted = (console as any)[symbol](args) as string;
-
- stream.moveCursor(0, formatted.split('\n').length);
- if (!replGone) {
- REPL.displayPrompt(true);
- }
- }
-
- return ret;
- };
-
- global.console.log = new Proxy(console.log, {
- apply: apply(process.stdout, kFormatForStdout)
- });
-
- global.console.warn = new Proxy(console.warn, {
- apply: apply(process.stderr, kFormatForStderr)
- });
-
- REPL.on('exit', () => {
- replGone = true;
- process.exit(0);
- });
-}
-
-/**
- * Parses the content surrounding by `<<>>` and emphasizes it with the given color or by making it bold.
- * @param content The content to parse.
- * @param color The color to emphasize the content with.
- * @param discordFormat Whether or not to format the content for discord.
- * @returns The formatted content.
- */
-function parseFormatting(
- content: any,
- color: 'blueBright' | 'blackBright' | 'redBright' | 'yellowBright' | 'greenBright' | '',
- discordFormat = false
-): string | typeof content {
- if (typeof content !== 'string') return content;
- return content
- .split(/<<|>>/)
- .map((value, index) => {
- if (discordFormat) {
- return index % 2 === 0 ? escapeMarkdown(value) : bold(escapeMarkdown(value));
- } else {
- return index % 2 === 0 || !color ? value : chalk[color](value);
- }
- })
- .join('');
-}
-
-/**
- * Inspects the content and returns a string.
- * @param content The content to inspect.
- * @param depth The depth the content will inspected. Defaults to `2`.
- * @param colors Whether or not to use colors in the output. Defaults to `true`.
- * @returns The inspected content.
- */
-function inspectContent(content: any, depth = 2, colors = true): string {
- if (typeof content !== 'string') {
- return inspect(content, { depth, colors });
- }
- return content;
-}
-
-/**
- * Generates a formatted timestamp for logging.
- * @returns The formatted timestamp.
- */
-function getTimeStamp(): string {
- const now = new Date();
- const minute = pad(now.getMinutes());
- const hour = pad(now.getHours());
- const date = `${pad(now.getMonth() + 1)}/${pad(now.getDate())}`;
- return `${date} ${hour}:${minute}`;
-}
-
-/**
- * Pad a two-digit number.
- */
-function pad(num: number) {
- return num.toString().padStart(2, '0');
-}
-
-/**
- * Custom logging utility for the bot.
- */
-export class BushLogger {
- /**
- * @param client The client.
- */
- public constructor(public client: Client) {}
-
- /**
- * Logs information. Highlight information by surrounding it in `<<>>`.
- * @param header The header displayed before the content, displayed in cyan.
- * @param content The content to log, highlights displayed in bright blue.
- * @param sendChannel Should this also be logged to discord? Defaults to false.
- * @param depth The depth the content will inspected. Defaults to 0.
- */
- public get log() {
- return this.info;
- }
-
- /**
- * Sends a message to the log channel.
- * @param message The parameter to pass to {@link PartialTextBasedChannelFields.send}.
- * @returns The message sent.
- */
- public async channelLog(message: SendMessageType): Promise<Message | null> {
- const channel = await this.client.utils.getConfigChannel('log');
- if (channel === null) return null;
- return await channel.send(message).catch(() => null);
- }
-
- /**
- * Sends a message to the error channel.
- * @param message The parameter to pass to {@link PartialTextBasedChannelFields.send}.
- * @returns The message sent.
- */
- public async channelError(message: SendMessageType): Promise<Message | null> {
- const channel = await this.client.utils.getConfigChannel('error');
- if (!channel) {
- void this.error(
- 'BushLogger',
- `Could not find error channel, was originally going to send: \n${inspect(message, {
- colors: true
- })}\n${new Error().stack?.substring(8)}`,
- false
- );
- return null;
- }
- return await channel.send(message);
- }
-
- /**
- * Logs debug information. Only works in dev is enabled in the config.
- * @param content The content to log.
- * @param depth The depth the content will inspected. Defaults to `0`.
- */
- public debug(content: any, depth = 0): void {
- if (!this.client.config.isDevelopment) return;
- const newContent = inspectContent(content, depth, true);
- console.log(`${chalk.bgMagenta(getTimeStamp())} ${chalk.magenta('[Debug]')} ${newContent}`);
- }
-
- /**
- * Logs raw debug information. Only works in dev is enabled in the config.
- * @param content The content to log.
- */
- public debugRaw(...content: any): void {
- if (!this.client.config.isDevelopment) return;
- console.log(`${chalk.bgMagenta(getTimeStamp())} ${chalk.magenta('[Debug]')}`, ...content);
- }
-
- /**
- * Logs verbose information. Highlight information by surrounding it in `<<>>`.
- * @param header The header printed before the content, displayed in grey.
- * @param content The content to log, highlights displayed in bright black.
- * @param sendChannel Should this also be logged to discord? Defaults to `false`.
- * @param depth The depth the content will inspected. Defaults to `0`.
- */
- public async verbose(header: string, content: any, sendChannel = false, depth = 0): Promise<void> {
- if (!this.client.config.logging.verbose) return;
- const newContent = inspectContent(content, depth, true);
- console.log(`${chalk.bgGrey(getTimeStamp())} ${chalk.grey(`[${header}]`)} ${parseFormatting(newContent, 'blackBright')}`);
- if (!sendChannel) return;
- const embed = new EmbedBuilder()
- .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`)
- .setColor(colors.gray)
- .setTimestamp();
- await this.channelLog({ embeds: [embed] });
- }
-
- /**
- * Logs very verbose information. Highlight information by surrounding it in `<<>>`.
- * @param header The header printed before the content, displayed in purple.
- * @param content The content to log, highlights displayed in bright black.
- * @param depth The depth the content will inspected. Defaults to `0`.
- */
- public async superVerbose(header: string, content: any, depth = 0): Promise<void> {
- if (!this.client.config.logging.verbose) return;
- const newContent = inspectContent(content, depth, true);
- console.log(
- `${chalk.bgHex('#949494')(getTimeStamp())} ${chalk.hex('#949494')(`[${header}]`)} ${chalk.hex('#b3b3b3')(newContent)}`
- );
- }
-
- /**
- * Logs raw very verbose information.
- * @param header The header printed before the content, displayed in purple.
- * @param content The content to log.
- */
- public async superVerboseRaw(header: string, ...content: any[]): Promise<void> {
- if (!this.client.config.logging.verbose) return;
- console.log(`${chalk.bgHex('#a3a3a3')(getTimeStamp())} ${chalk.hex('#a3a3a3')(`[${header}]`)}`, ...content);
- }
-
- /**
- * Logs information. Highlight information by surrounding it in `<<>>`.
- * @param header The header displayed before the content, displayed in cyan.
- * @param content The content to log, highlights displayed in bright blue.
- * @param sendChannel Should this also be logged to discord? Defaults to `false`.
- * @param depth The depth the content will inspected. Defaults to `0`.
- */
- public async info(header: string, content: any, sendChannel = true, depth = 0): Promise<void> {
- if (!this.client.config.logging.info) return;
- const newContent = inspectContent(content, depth, true);
- console.log(`${chalk.bgCyan(getTimeStamp())} ${chalk.cyan(`[${header}]`)} ${parseFormatting(newContent, 'blueBright')}`);
- if (!sendChannel) return;
- const embed = new EmbedBuilder()
- .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`)
- .setColor(colors.info)
- .setTimestamp();
- await this.channelLog({ embeds: [embed] });
- }
-
- /**
- * Logs warnings. Highlight information by surrounding it in `<<>>`.
- * @param header The header displayed before the content, displayed in yellow.
- * @param content The content to log, highlights displayed in bright yellow.
- * @param sendChannel Should this also be logged to discord? Defaults to `false`.
- * @param depth The depth the content will inspected. Defaults to `0`.
- */
- public async warn(header: string, content: any, sendChannel = true, depth = 0): Promise<void> {
- const newContent = inspectContent(content, depth, true);
- console.warn(
- `${chalk.bgYellow(getTimeStamp())} ${chalk.yellow(`[${header}]`)} ${parseFormatting(newContent, 'yellowBright')}`
- );
-
- if (!sendChannel) return;
- const embed = new EmbedBuilder()
- .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`)
- .setColor(colors.warn)
- .setTimestamp();
- await this.channelError({ embeds: [embed] });
- }
-
- /**
- * Logs errors. Highlight information by surrounding it in `<<>>`.
- * @param header The header displayed before the content, displayed in bright red.
- * @param content The content to log, highlights displayed in bright red.
- * @param sendChannel Should this also be logged to discord? Defaults to `false`.
- * @param depth The depth the content will inspected. Defaults to `0`.
- */
- public async error(header: string, content: any, sendChannel = true, depth = 0): Promise<void> {
- const newContent = inspectContent(content, depth, true);
- console.warn(
- `${chalk.bgRedBright(getTimeStamp())} ${chalk.redBright(`[${header}]`)} ${parseFormatting(newContent, 'redBright')}`
- );
- if (!sendChannel) return;
- const embed = new EmbedBuilder()
- .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`)
- .setColor(colors.error)
- .setTimestamp();
- await this.channelError({ embeds: [embed] });
- return;
- }
-
- /**
- * Logs successes. Highlight information by surrounding it in `<<>>`.
- * @param header The header displayed before the content, displayed in green.
- * @param content The content to log, highlights displayed in bright green.
- * @param sendChannel Should this also be logged to discord? Defaults to `false`.
- * @param depth The depth the content will inspected. Defaults to `0`.
- */
- public async success(header: string, content: any, sendChannel = true, depth = 0): Promise<void> {
- const newContent = inspectContent(content, depth, true);
- console.log(
- `${chalk.bgGreen(getTimeStamp())} ${chalk.greenBright(`[${header}]`)} ${parseFormatting(newContent, 'greenBright')}`
- );
- if (!sendChannel) return;
- const embed = new EmbedBuilder()
- .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`)
- .setColor(colors.success)
- .setTimestamp();
- await this.channelLog({ embeds: [embed] }).catch(() => {});
- }
-}
diff --git a/src/lib/utils/BushUtils.ts b/src/lib/utils/BushUtils.ts
deleted file mode 100644
index 75aded3..0000000
--- a/src/lib/utils/BushUtils.ts
+++ /dev/null
@@ -1,612 +0,0 @@
-import {
- Arg,
- BushClient,
- CommandMessage,
- SlashEditMessageType,
- SlashSendMessageType,
- timeUnits,
- type BaseBushArgumentType,
- type BushInspectOptions,
- type SlashMessage
-} from '#lib';
-import { humanizeDuration as humanizeDurationMod } from '@notenoughupdates/humanize-duration';
-import assert from 'assert/strict';
-import cp from 'child_process';
-import deepLock from 'deep-lock';
-import { Util as AkairoUtil } from 'discord-akairo';
-import {
- Constants as DiscordConstants,
- EmbedBuilder,
- Message,
- OAuth2Scopes,
- PermissionFlagsBits,
- PermissionsBitField,
- type APIEmbed,
- type APIMessage,
- type CommandInteraction,
- 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 '../common/util/Format.js';
-
-export type StripPrivate<T> = { [K in keyof T]: T[K] extends Record<string, any> ? StripPrivate<T[K]> : T[K] };
-export type ValueOf<T> = T[keyof T];
-
-/**
- * Capitalizes the first letter of the given text
- * @param text The text to capitalize
- * @returns The capitalized text
- */
-export function capitalize(text: string): string {
- return text.charAt(0).toUpperCase() + text.slice(1);
-}
-
-export const exec = promisify(cp.exec);
-
-/**
- * Runs a shell command and gives the output
- * @param command The shell command to run
- * @returns The stdout and stderr of the shell command
- */
-export async function shell(command: string): Promise<{ stdout: string; stderr: string }> {
- return await exec(command);
-}
-
-/**
- * Appends the correct ordinal to the given number
- * @param n The number to append an ordinal to
- * @returns The number with the ordinal
- */
-export function ordinal(n: number): string {
- const s = ['th', 'st', 'nd', 'rd'],
- v = n % 100;
- return n + (s[(v - 20) % 10] || s[v] || s[0]);
-}
-
-/**
- * Chunks an array to the specified size
- * @param arr The array to chunk
- * @param perChunk The amount of items per chunk
- * @returns The chunked array
- */
-export function chunk<T>(arr: T[], perChunk: number): T[][] {
- return arr.reduce((all, one, i) => {
- const ch: number = Math.floor(i / perChunk);
- (all as any[])[ch] = [].concat(all[ch] || [], one as any);
- return all;
- }, []);
-}
-
-/**
- * Fetches a user's uuid from the mojang api.
- * @param username The username to get the uuid of.
- * @returns The the uuid of the user.
- */
-export async function mcUUID(username: string, dashed = false): Promise<string> {
- const apiRes = (await got.get(`https://api.ashcon.app/mojang/v2/user/${username}`).json()) as UuidRes;
- return dashed ? apiRes.uuid : apiRes.uuid.replace(/-/g, '');
-}
-
-export interface UuidRes {
- uuid: string;
- username: string;
- username_history?: { username: string }[] | null;
- textures: {
- custom: boolean;
- slim: boolean;
- skin: {
- url: string;
- data: string;
- };
- raw: {
- value: string;
- signature: string;
- };
- };
- created_at: string;
-}
-
-/**
- * Generate defaults for {@link inspect}.
- * @param options The options to create defaults with.
- * @returns The default options combined with the specified options.
- */
-function getDefaultInspectOptions(options?: BushInspectOptions): BushInspectOptions {
- return {
- showHidden: options?.showHidden ?? false,
- depth: options?.depth ?? 2,
- colors: options?.colors ?? false,
- customInspect: options?.customInspect ?? true,
- showProxy: options?.showProxy ?? false,
- maxArrayLength: options?.maxArrayLength ?? Infinity,
- maxStringLength: options?.maxStringLength ?? Infinity,
- breakLength: options?.breakLength ?? 80,
- compact: options?.compact ?? 3,
- sorted: options?.sorted ?? false,
- getters: options?.getters ?? true,
- numericSeparator: options?.numericSeparator ?? true
- };
-}
-
-/**
- * Uses {@link inspect} with custom defaults.
- * @param object - The object you would like to inspect.
- * @param options - The options you would like to use to inspect the object.
- * @returns The inspected object.
- */
-export function inspect(object: any, options?: BushInspectOptions): string {
- const optionsWithDefaults = getDefaultInspectOptions(options);
-
- if (!optionsWithDefaults.inspectStrings && typeof object === 'string') return object;
-
- return inspectUtil(object, optionsWithDefaults);
-}
-
-/**
- * Responds to a slash command interaction.
- * @param interaction The interaction to respond to.
- * @param responseOptions The options for the response.
- * @returns The message sent.
- */
-export async function slashRespond(
- interaction: CommandInteraction,
- responseOptions: SlashSendMessageType | SlashEditMessageType
-): Promise<Message | APIMessage | undefined> {
- const newResponseOptions = typeof responseOptions === 'string' ? { content: responseOptions } : responseOptions;
- if (interaction.replied || interaction.deferred) {
- delete (newResponseOptions as InteractionReplyOptions).ephemeral; // Cannot change a preexisting message to be ephemeral
- return (await interaction.editReply(newResponseOptions)) as Message | APIMessage;
- } else {
- await interaction.reply(newResponseOptions);
- return await interaction.fetchReply().catch(() => undefined);
- }
-}
-
-/**
- * Takes an array and combines the elements using the supplied conjunction.
- * @param array The array to combine.
- * @param conjunction The conjunction to use.
- * @param ifEmpty What to return if the array is empty.
- * @returns The combined elements or `ifEmpty`.
- *
- * @example
- * const permissions = oxford(['Administrator', 'SendMessages', 'ManageMessages'], 'and', 'none');
- * console.log(permissions); // Administrator, SendMessages and ManageMessages
- */
-export function oxford(array: string[], conjunction: string, ifEmpty?: string): string | undefined {
- const l = array.length;
- if (!l) return ifEmpty;
- if (l < 2) return array[0];
- if (l < 3) return array.join(` ${conjunction} `);
- array = array.slice();
- array[l - 1] = `${conjunction} ${array[l - 1]}`;
- return array.join(', ');
-}
-
-/**
- * Add or remove an item from an array. All duplicates will be removed.
- * @param action Either `add` or `remove` an element.
- * @param array The array to add/remove an element from.
- * @param value The element to add/remove from the array.
- */
-export function addOrRemoveFromArray<T>(action: 'add' | 'remove', array: T[], value: T): T[] {
- const set = new Set(array);
- action === 'add' ? set.add(value) : set.delete(value);
- return [...set];
-}
-
-/**
- * Remove an item from an array. All duplicates will be removed.
- * @param array The array to remove an element from.
- * @param value The element to remove from the array.
- */
-export function removeFromArray<T>(array: T[], value: T): T[] {
- return addOrRemoveFromArray('remove', array, value);
-}
-
-/**
- * Add an item from an array. All duplicates will be removed.
- * @param array The array to add an element to.
- * @param value The element to add to the array.
- */
-export function addToArray<T>(array: T[], value: T): T[] {
- return addOrRemoveFromArray('add', array, value);
-}
-
-/**
- * Surrounds a string to the begging an end of each element in an array.
- * @param array The array you want to surround.
- * @param surroundChar1 The character placed in the beginning of the element.
- * @param surroundChar2 The character placed in the end of the element. Defaults to `surroundChar1`.
- */
-export function surroundArray(array: string[], surroundChar1: string, surroundChar2?: string): string[] {
- return array.map((a) => `${surroundChar1}${a}${surroundChar2 ?? surroundChar1}`);
-}
-
-/**
- * Gets the duration from a specified string.
- * @param content The string to look for a duration in.
- * @param remove Whether or not to remove the duration from the original string.
- * @returns The {@link ParsedDuration}.
- */
-export function parseDuration(content: string, remove = true): ParsedDuration {
- if (!content) return { duration: 0, content: null };
-
- // eslint-disable-next-line prefer-const
- let duration: number | null = null;
- // Try to reduce false positives by requiring a space before the duration, this makes sure it still matches if it is
- // in the beginning of the argument
- let contentWithoutTime = ` ${content}`;
-
- for (const unit in timeUnits) {
- const regex = timeUnits[unit as keyof typeof timeUnits].match;
- const match = regex.exec(contentWithoutTime);
- const value = Number(match?.groups?.[unit]);
- if (!isNaN(value)) duration! += value * timeUnits[unit as keyof typeof timeUnits].value;
-
- if (remove) contentWithoutTime = contentWithoutTime.replace(regex, '');
- }
- // remove the space added earlier
- if (contentWithoutTime.startsWith(' ')) contentWithoutTime.replace(' ', '');
- return { duration, content: contentWithoutTime };
-}
-
-export interface ParsedDuration {
- duration: number | null;
- content: string | null;
-}
-
-/**
- * Converts a duration in milliseconds to a human readable form.
- * @param duration The duration in milliseconds to convert.
- * @param largest The maximum number of units to display for the duration.
- * @param round Whether or not to round the smallest unit displayed.
- * @returns A humanized string of the duration.
- */
-export function humanizeDuration(duration: number, largest?: number, round = true): string {
- if (largest) return humanizeDurationMod(duration, { language: 'en', maxDecimalPoints: 2, largest, round })!;
- else return humanizeDurationMod(duration, { language: 'en', maxDecimalPoints: 2, round })!;
-}
-
-/**
- * Creates a formatted relative timestamp from a duration in milliseconds.
- * @param duration The duration in milliseconds.
- * @returns The formatted relative timestamp.
- */
-export function timestampDuration(duration: number): string {
- return `<t:${Math.round(new Date().getTime() / 1_000 + duration / 1_000)}:R>`;
-}
-
-/**
- * Creates a timestamp from a date.
- * @param date The date to create a timestamp from.
- * @param style The style of the timestamp.
- * @returns The formatted timestamp.
- *
- * @see
- * **Styles:**
- * - **t**: Short Time ex. `16:20`
- * - **T**: Long Time ex. `16:20:30 `
- * - **d**: Short Date ex. `20/04/2021`
- * - **D**: Long Date ex. `20 April 2021`
- * - **f**: Short Date/Time ex. `20 April 2021 16:20`
- * - **F**: Long Date/Time ex. `Tuesday, 20 April 2021 16:20`
- * - **R**: Relative Time ex. `2 months ago`
- */
-export function timestamp<D extends Date | undefined | null>(
- date: D,
- style: TimestampStyle = 'f'
-): D extends Date ? string : undefined {
- if (!date) return date as unknown as D extends Date ? string : undefined;
- return `<t:${Math.round(date.getTime() / 1_000)}:${style}>` as unknown as D extends Date ? string : undefined;
-}
-
-export type TimestampStyle = 't' | 'T' | 'd' | 'D' | 'f' | 'F' | 'R';
-
-/**
- * Creates a human readable representation between a date and the current time.
- * @param date The date to be compared with the current time.
- * @param largest The maximum number of units to display for the duration.
- * @param round Whether or not to round the smallest unit displayed.
- * @returns A humanized string of the delta.
- */
-export function dateDelta(date: Date, largest = 3, round = true): string {
- return humanizeDuration(new Date().getTime() - date.getTime(), largest, round);
-}
-
-/**
- * Combines {@link timestamp} and {@link dateDelta}
- * @param date The date to be compared with the current time.
- * @param style The style of the timestamp.
- * @returns The formatted timestamp.
- *
- * @see
- * **Styles:**
- * - **t**: Short Time ex. `16:20`
- * - **T**: Long Time ex. `16:20:30 `
- * - **d**: Short Date ex. `20/04/2021`
- * - **D**: Long Date ex. `20 April 2021`
- * - **f**: Short Date/Time ex. `20 April 2021 16:20`
- * - **F**: Long Date/Time ex. `Tuesday, 20 April 2021 16:20`
- * - **R**: Relative Time ex. `2 months ago`
- */
-export function timestampAndDelta(date: Date, style: TimestampStyle = 'D'): string {
- return `${timestamp(date, style)} (${dateDelta(date)} ago)`;
-}
-
-/**
- * Convert a hex code to an rbg value.
- * @param hex The hex code to convert.
- * @returns The rbg value.
- */
-export function hexToRgb(hex: string): string {
- const arrBuff = new ArrayBuffer(4);
- const vw = new DataView(arrBuff);
- vw.setUint32(0, parseInt(hex, 16), false);
- const arrByte = new Uint8Array(arrBuff);
-
- return `${arrByte[1]}, ${arrByte[2]}, ${arrByte[3]}`;
-}
-
-/**
- * Wait an amount in milliseconds.
- * @returns A promise that resolves after the specified amount of milliseconds
- */
-export const sleep = promisify(setTimeout);
-
-/**
- * List the methods of an object.
- * @param obj The object to get the methods of.
- * @returns A string with each method on a new line.
- */
-export function getMethods(obj: Record<string, any>): string {
- // modified from https://stackoverflow.com/questions/31054910/get-functions-methods-of-a-class/31055217#31055217
- let props: string[] = [];
- let obj_: Record<string, any> = new Object(obj);
-
- do {
- const l = Object.getOwnPropertyNames(obj_)
- .concat(Object.getOwnPropertySymbols(obj_).map((s) => s.toString()))
- .sort()
- .filter(
- (p, i, arr) =>
- typeof Object.getOwnPropertyDescriptor(obj_, p)?.['get'] !== 'function' && // ignore getters
- typeof Object.getOwnPropertyDescriptor(obj_, p)?.['set'] !== 'function' && // ignore setters
- typeof obj_[p] === 'function' && // only the methods
- p !== 'constructor' && // not the constructor
- (i == 0 || p !== arr[i - 1]) && // not overriding in this prototype
- props.indexOf(p) === -1 // not overridden in a child
- );
-
- const reg = /\(([\s\S]*?)\)/;
- props = props.concat(
- l.map(
- (p) =>
- `${obj_[p] && obj_[p][Symbol.toStringTag] === 'AsyncFunction' ? 'async ' : ''}function ${p}(${
- reg.exec(obj_[p].toString())?.[1]
- ? reg
- .exec(obj_[p].toString())?.[1]
- .split(', ')
- .map((arg) => arg.split('=')[0].trim())
- .join(', ')
- : ''
- });`
- )
- );
- } while (
- (obj_ = Object.getPrototypeOf(obj_)) && // walk-up the prototype chain
- Object.getPrototypeOf(obj_) // not the the Object prototype methods (hasOwnProperty, etc...)
- );
-
- return props.join('\n');
-}
-
-/**
- * List the symbols of an object.
- * @param obj The object to get the symbols of.
- * @returns An array of the symbols of the object.
- */
-export function getSymbols(obj: Record<string, any>): symbol[] {
- let symbols: symbol[] = [];
- let obj_: Record<string, any> = new Object(obj);
-
- do {
- const l = Object.getOwnPropertySymbols(obj_).sort();
-
- symbols = [...symbols, ...l];
- } while (
- (obj_ = Object.getPrototypeOf(obj_)) && // walk-up the prototype chain
- Object.getPrototypeOf(obj_) // not the the Object prototype methods (hasOwnProperty, etc...)
- );
-
- return symbols;
-}
-
-/**
- * Checks if a user has a certain guild permission (doesn't check channel permissions).
- * @param message The message to check the user from.
- * @param permissions The permissions to check for.
- * @returns The missing permissions or null if none are missing.
- */
-export function userGuildPermCheck(
- message: CommandMessage | SlashMessage,
- permissions: typeof PermissionFlagsBits[keyof typeof PermissionFlagsBits][]
-): PermissionsString[] | null {
- if (!message.inGuild()) return null;
- const missing = message.member?.permissions.missing(permissions) ?? [];
-
- return missing.length ? missing : null;
-}
-
-/**
- * Check if the client has certain permissions in the guild (doesn't check channel permissions).
- * @param message The message to check the client user from.
- * @param permissions The permissions to check for.
- * @returns The missing permissions or null if none are missing.
- */
-export function clientGuildPermCheck(message: CommandMessage | SlashMessage, permissions: bigint[]): PermissionsString[] | null {
- const missing = message.guild?.members.me?.permissions.missing(permissions) ?? [];
-
- return missing.length ? missing : null;
-}
-
-/**
- * Check if the client has permission to send messages in the channel as well as check if they have other permissions
- * in the guild (or the channel if `checkChannel` is `true`).
- * @param message The message to check the client user from.
- * @param permissions The permissions to check for.
- * @param checkChannel Whether to check the channel permissions instead of the guild permissions.
- * @returns The missing permissions or null if none are missing.
- */
-export function clientSendAndPermCheck(
- message: CommandMessage | SlashMessage,
- permissions: bigint[] = [],
- checkChannel = false
-): PermissionsString[] | null {
- if (!message.inGuild() || !message.channel) return null;
-
- const missing: PermissionsString[] = [];
- const sendPerm = message.channel.isThread() ? 'SendMessages' : 'SendMessagesInThreads';
-
- // todo: remove once forum channels are fixed
- if (message.channel.parent === null && message.channel.isThread()) return null;
-
- if (!message.guild.members.me!.permissionsIn(message.channel!.id).has(sendPerm)) missing.push(sendPerm);
-
- missing.push(
- ...(checkChannel
- ? message.guild!.members.me!.permissionsIn(message.channel!.id!).missing(permissions)
- : clientGuildPermCheck(message, permissions) ?? [])
- );
-
- return missing.length ? missing : null;
-}
-
-export { deepLock as deepFreeze };
-export { Arg as arg };
-export { Format as format };
-export { DiscordConstants as discordConstants };
-export { AkairoUtil as akairo };
-
-/**
- * The link to invite the bot with all permissions.
- */
-export function invite(client: BushClient) {
- return client.generateInvite({
- permissions:
- PermissionsBitField.All -
- PermissionFlagsBits.UseEmbeddedActivities -
- PermissionFlagsBits.ViewGuildInsights -
- PermissionFlagsBits.Stream,
- scopes: [OAuth2Scopes.Bot, OAuth2Scopes.ApplicationsCommands]
- });
-}
-
-/**
- * Asset multiple statements at a time.
- * @param args
- */
-export function assertAll(...args: any[]): void {
- for (let i = 0; i < args.length; i++) {
- assert(args[i], `assertAll index ${i} failed`);
- }
-}
-
-/**
- * Casts a string to a duration and reason for slash commands.
- * @param arg The argument received.
- * @param message The message that triggered the command.
- * @returns The casted argument.
- */
-export async function castDurationContent(
- arg: string | ParsedDuration | null,
- message: CommandMessage | SlashMessage
-): Promise<ParsedDurationRes> {
- const res = typeof arg === 'string' ? await Arg.cast('contentWithDuration', message, arg) : arg;
-
- return { duration: res?.duration ?? 0, content: res?.content ?? '' };
-}
-
-export interface ParsedDurationRes {
- duration: number;
- content: string;
-}
-
-/**
- * Casts a string to a the specified argument type.
- * @param type The type of the argument to cast to.
- * @param arg The argument received.
- * @param message The message that triggered the command.
- * @returns The casted argument.
- */
-export async function cast<T extends keyof BaseBushArgumentType>(
- type: T,
- arg: BaseBushArgumentType[T] | string,
- message: CommandMessage | SlashMessage
-) {
- return typeof arg === 'string' ? await Arg.cast(type, message, arg) : arg;
-}
-
-/**
- * Overflows the description of an embed into multiple embeds.
- * @param embed The options to be applied to the (first) embed.
- * @param lines Each line of the description as an element in an array.
- */
-export function overflowEmbed(embed: Omit<APIEmbed, 'description'>, lines: string[], maxLength = 4096): EmbedBuilder[] {
- const embeds: EmbedBuilder[] = [];
-
- const makeEmbed = () => {
- embeds.push(new EmbedBuilder().setColor(embed.color ?? null));
- return embeds.at(-1)!;
- };
-
- for (const line of lines) {
- let current = embeds.length ? embeds.at(-1)! : makeEmbed();
- let joined = current.data.description ? `${current.data.description}\n${line}` : line;
- if (joined.length > maxLength) {
- current = makeEmbed();
- joined = line;
- }
-
- current.setDescription(joined);
- }
-
- if (!embeds.length) makeEmbed();
-
- if (embed.author) embeds.at(0)?.setAuthor(embed.author);
- if (embed.title) embeds.at(0)?.setTitle(embed.title);
- if (embed.url) embeds.at(0)?.setURL(embed.url);
- if (embed.fields) embeds.at(-1)?.setFields(embed.fields);
- if (embed.thumbnail) embeds.at(-1)?.setThumbnail(embed.thumbnail.url);
- if (embed.footer) embeds.at(-1)?.setFooter(embed.footer);
- if (embed.image) embeds.at(-1)?.setImage(embed.image.url);
- if (embed.timestamp) embeds.at(-1)?.setTimestamp(new Date(embed.timestamp));
-
- return embeds;
-}
-
-/**
- * Formats an error into a string.
- * @param error The error to format.
- * @param colors Whether to use colors in the output.
- * @returns The formatted error.
- */
-export function formatError(error: Error | any, colors = false): string {
- if (!error) return error;
- if (typeof error !== 'object') return String.prototype.toString.call(error);
- if (
- getSymbols(error)
- .map((s) => s.toString())
- .includes('Symbol(nodejs.util.inspect.custom)')
- )
- return inspect(error, { colors });
-
- return error.stack;
-}
-
-export function deepWriteable<T>(obj: T): DeepWritable<T> {
- return obj as DeepWritable<T>;
-}
diff --git a/src/lib/utils/CanvasProgressBar.ts b/src/lib/utils/CanvasProgressBar.ts
deleted file mode 100644
index fb4f778..0000000
--- a/src/lib/utils/CanvasProgressBar.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { CanvasRenderingContext2D } from 'canvas';
-
-/**
- * I just copy pasted this code from stackoverflow don't yell at me if there is issues for it
- * @author @TymanWasTaken
- */
-export class CanvasProgressBar {
- private readonly x: number;
- private readonly y: number;
- private readonly w: number;
- private readonly h: number;
- private readonly color: string;
- private percentage: number;
- private p?: number;
- private ctx: CanvasRenderingContext2D;
-
- public constructor(
- ctx: CanvasRenderingContext2D,
- dimension: { x: number; y: number; width: number; height: number },
- color: string,
- percentage: number
- ) {
- ({ x: this.x, y: this.y, width: this.w, height: this.h } = dimension);
- this.color = color;
- this.percentage = percentage;
- this.p = undefined;
- this.ctx = ctx;
- }
-
- public draw(): void {
- // -----------------
- this.p = this.percentage * this.w;
- if (this.p <= this.h) {
- this.ctx.beginPath();
- this.ctx.arc(
- this.h / 2 + this.x,
- this.h / 2 + this.y,
- this.h / 2,
- Math.PI - Math.acos((this.h - this.p) / this.h),
- Math.PI + Math.acos((this.h - this.p) / this.h)
- );
- this.ctx.save();
- this.ctx.scale(-1, 1);
- this.ctx.arc(
- this.h / 2 - this.p - this.x,
- this.h / 2 + this.y,
- this.h / 2,
- Math.PI - Math.acos((this.h - this.p) / this.h),
- Math.PI + Math.acos((this.h - this.p) / this.h)
- );
- this.ctx.restore();
- this.ctx.closePath();
- } else {
- this.ctx.beginPath();
- this.ctx.arc(this.h / 2 + this.x, this.h / 2 + this.y, this.h / 2, Math.PI / 2, (3 / 2) * Math.PI);
- this.ctx.lineTo(this.p - this.h + this.x, 0 + this.y);
- this.ctx.arc(this.p - this.h / 2 + this.x, this.h / 2 + this.y, this.h / 2, (3 / 2) * Math.PI, Math.PI / 2);
- this.ctx.lineTo(this.h / 2 + this.x, this.h + this.y);
- this.ctx.closePath();
- }
- this.ctx.fillStyle = this.color;
- this.ctx.fill();
- }
-
- // public showWholeProgressBar(){
- // this.ctx.beginPath();
- // this.ctx.arc(this.h / 2 + this.x, this.h / 2 + this.y, this.h / 2, Math.PI / 2, 3 / 2 * Math.PI);
- // this.ctx.lineTo(this.w - this.h + this.x, 0 + this.y);
- // this.ctx.arc(this.w - this.h / 2 + this.x, this.h / 2 + this.y, this.h / 2, 3 / 2 *Math.PI, Math.PI / 2);
- // this.ctx.lineTo(this.h / 2 + this.x, this.h + this.y);
- // this.ctx.strokeStyle = '#000000';
- // this.ctx.stroke();
- // this.ctx.closePath();
- // }
-
- public get PPercentage(): number {
- return this.percentage * 100;
- }
-
- public set PPercentage(x: number) {
- this.percentage = x / 100;
- }
-}
diff --git a/src/listeners/message/automodCreate.ts b/src/listeners/automod/automodCreate.ts
index 0f089a3..529651c 100644
--- a/src/listeners/message/automodCreate.ts
+++ b/src/listeners/automod/automodCreate.ts
@@ -1,4 +1,4 @@
-import { AutoMod, BushListener, type BushClientEvents } from '#lib';
+import { BushListener, MessageAutomod, type BushClientEvents } from '#lib';
export default class AutomodMessageCreateListener extends BushListener {
public constructor() {
@@ -10,6 +10,7 @@ export default class AutomodMessageCreateListener extends BushListener {
}
public async exec(...[message]: BushClientEvents['messageCreate']) {
- return new AutoMod(message);
+ if (message.member === null) return;
+ return new MessageAutomod(message);
}
}
diff --git a/src/listeners/message/automodUpdate.ts b/src/listeners/automod/automodUpdate.ts
index fa00f92..d68759f 100644
--- a/src/listeners/message/automodUpdate.ts
+++ b/src/listeners/automod/automodUpdate.ts
@@ -1,4 +1,4 @@
-import { AutoMod, BushListener, type BushClientEvents } from '#lib';
+import { BushListener, MessageAutomod, type BushClientEvents } from '#lib';
export default class AutomodMessageUpdateListener extends BushListener {
public constructor() {
@@ -11,7 +11,7 @@ export default class AutomodMessageUpdateListener extends BushListener {
public async exec(...[_, newMessage]: BushClientEvents['messageUpdate']) {
const fullMessage = newMessage.partial ? await newMessage.fetch().catch(() => null) : newMessage;
- if (!fullMessage) return;
- return new AutoMod(fullMessage);
+ if (!fullMessage?.member) return;
+ return new MessageAutomod(fullMessage);
}
}
diff --git a/src/listeners/automod/memberAutomod.ts b/src/listeners/automod/memberAutomod.ts
new file mode 100644
index 0000000..01eb56c
--- /dev/null
+++ b/src/listeners/automod/memberAutomod.ts
@@ -0,0 +1,21 @@
+import { BushClientEvents, BushListener, MemberAutomod } from '#lib';
+import chalk from 'chalk';
+
+export default class PresenceAutomodListener extends BushListener {
+ public constructor() {
+ super('memberAutomod', {
+ emitter: 'client',
+ event: 'guildMemberUpdate'
+ });
+ }
+
+ public async exec(...[_, newMember]: BushClientEvents['guildMemberUpdate']) {
+ if (!(await newMember.guild.hasFeature('automodMembers'))) return;
+ if (!(await newMember.guild.hasFeature('automod'))) return;
+
+ new MemberAutomod(newMember);
+ console.log(
+ `${chalk.hex('#ff7105')('[MemberAutomod]')} Created a new MemberAutomod for ${newMember.user.tag} (${newMember.user.id})`
+ );
+ }
+}
diff --git a/src/listeners/automod/presenceAutomod.ts b/src/listeners/automod/presenceAutomod.ts
new file mode 100644
index 0000000..f361508
--- /dev/null
+++ b/src/listeners/automod/presenceAutomod.ts
@@ -0,0 +1,27 @@
+import { BushClientEvents, BushListener, PresenceAutomod } from '#lib';
+import chalk from 'chalk';
+
+/* export default */ class PresenceAutomodListener extends BushListener {
+ public constructor() {
+ super('presenceAutomod', {
+ emitter: 'client',
+ event: 'presenceUpdate'
+ });
+ }
+
+ public async exec(...[_, newPresence]: BushClientEvents['presenceUpdate']) {
+ if (!newPresence.member || !newPresence.guild) return;
+
+ if (!newPresence.activities.length) return;
+
+ if (!(await newPresence.guild.hasFeature('automodPresence'))) return;
+ if (!(await newPresence.guild.hasFeature('automod'))) return;
+
+ new PresenceAutomod(newPresence);
+ console.log(
+ `${chalk.hex('#ffe605')('[PresenceAutomod]')} Created a new PresenceAutomod for ${newPresence.member.user.tag} (${
+ newPresence.member.user.id
+ })`
+ );
+ }
+}
diff --git a/src/listeners/commands/commandError.ts b/src/listeners/commands/commandError.ts
index f12881a..bfa857c 100644
--- a/src/listeners/commands/commandError.ts
+++ b/src/listeners/commands/commandError.ts
@@ -1,8 +1,8 @@
import { capitalize, colors, format, formatError, SlashMessage, type BushCommandHandlerEvents } from '#lib';
import { type AkairoMessage, type Command } from 'discord-akairo';
import { ChannelType, Client, EmbedBuilder, escapeInlineCode, GuildTextBasedChannel, type Message } from 'discord.js';
-import { bold } from '../../lib/common/util/Format.js';
-import { BushListener } from '../../lib/extensions/discord-akairo/BushListener.js';
+import { BushListener } from '../../../lib/extensions/discord-akairo/BushListener.js';
+import { bold } from '../../../lib/utils/Format.js';
export default class CommandErrorListener extends BushListener {
public constructor() {
diff --git a/src/listeners/interaction/interactionCreate.ts b/src/listeners/interaction/interactionCreate.ts
index 91bcae6..8dd753b 100644
--- a/src/listeners/interaction/interactionCreate.ts
+++ b/src/listeners/interaction/interactionCreate.ts
@@ -1,4 +1,4 @@
-import { AutoMod, BushListener, emojis, format, oxford, surroundArray, type BushClientEvents } from '#lib';
+import { BushListener, emojis, format, handleAutomodInteraction, oxford, surroundArray, type BushClientEvents } from '#lib';
import { InteractionType } from 'discord.js';
export default class InteractionCreateListener extends BushListener {
@@ -22,7 +22,7 @@ export default class InteractionCreateListener extends BushListener {
} else if (interaction.isButton()) {
const id = interaction.customId;
if (['paginate_', 'command_', 'confirmationPrompt_', 'appeal'].some((s) => id.startsWith(s))) return;
- else if (id.startsWith('automod;')) void AutoMod.handleInteraction(interaction);
+ else if (id.startsWith('automod;')) void handleAutomodInteraction(interaction);
else if (id.startsWith('button-role;') && interaction.inCachedGuild()) {
const [, roleId] = id.split(';');
const role = interaction.guild.roles.cache.get(roleId);
diff --git a/src/listeners/member-custom/bushLevelUpdate.ts b/src/listeners/member-custom/bushLevelUpdate.ts
index 0281288..702f7cc 100644
--- a/src/listeners/member-custom/bushLevelUpdate.ts
+++ b/src/listeners/member-custom/bushLevelUpdate.ts
@@ -52,10 +52,12 @@ export default class BushLevelUpdateListener extends BushListener {
}
try {
if (promises.length) await Promise.all(promises);
- } catch (e) {
+ } catch (e: any) {
await member.guild.error(
'bushLevelUpdate',
- `There was an error adding level roles to ${member.user.tag} upon reaching to level ${newLevel}.\n${e?.message ?? e}`
+ `There was an error adding level roles to ${member.user.tag} upon reaching to level ${newLevel}.\n${
+ 'message' in e ? e.message : e
+ }`
);
}
}
diff --git a/src/tasks/cache/updateCache.ts b/src/tasks/cache/updateCache.ts
index 87636e8..595a872 100644
--- a/src/tasks/cache/updateCache.ts
+++ b/src/tasks/cache/updateCache.ts
@@ -1,8 +1,8 @@
-import { Time } from '#constants';
import { Global, Guild, Shared, type BushClient } from '#lib';
import type { Client } from 'discord.js';
import config from '../../../config/options.js';
-import { BushTask } from '../../lib/extensions/discord-akairo/BushTask.js';
+import { BushTask } from '../../../lib/extensions/discord-akairo/BushTask.js';
+import { Time } from '../../../lib/utils/BushConstants.js';
export default class UpdateCacheTask extends BushTask {
public constructor() {
diff --git a/src/tasks/cache/updateHighlightCache.ts b/src/tasks/cache/updateHighlightCache.ts
index 44ddd90..4ab5544 100644
--- a/src/tasks/cache/updateHighlightCache.ts
+++ b/src/tasks/cache/updateHighlightCache.ts
@@ -1,5 +1,5 @@
-import { Time } from '#constants';
-import { BushTask } from '../../lib/extensions/discord-akairo/BushTask.js';
+import { BushTask } from '../../../lib/extensions/discord-akairo/BushTask.js';
+import { Time } from '../../../lib/utils/BushConstants.js';
export default class UpdateHighlightCacheTask extends BushTask {
public constructor() {
diff --git a/src/tasks/cache/updatePriceItemCache.ts b/src/tasks/cache/updatePriceItemCache.ts
index 9809cbd..55115cc 100644
--- a/src/tasks/cache/updatePriceItemCache.ts
+++ b/src/tasks/cache/updatePriceItemCache.ts
@@ -12,7 +12,12 @@ export default class UpdatePriceItemCache extends BushTask {
public async exec() {
const [bazaar, currentLowestBIN, averageLowestBIN, auctionAverages] = (await Promise.all(
- PriceCommand.urls.map(({ url }) => got.get(url).json().catch(undefined))
+ PriceCommand.urls.map(({ url }) =>
+ got
+ .get(url)
+ .json()
+ .catch(() => undefined)
+ )
)) as [Bazaar?, LowestBIN?, LowestBIN?, AuctionAverages?];
const itemNames = new Set([
diff --git a/src/tasks/feature/handleReminders.ts b/src/tasks/feature/handleReminders.ts
index 7863c9a..1e44083 100644
--- a/src/tasks/feature/handleReminders.ts
+++ b/src/tasks/feature/handleReminders.ts
@@ -29,7 +29,8 @@ export default class HandlerRemindersTask extends BushTask {
void this.client.users
.send(
entry.user,
- `The reminder you set ${dateDelta(entry.created)} ago has expired: ${format.bold(entry.content)}\n${entry.messageUrl}`
+ `The reminder you set ${dateDelta(entry.created)} ago has expired: ${format.bold(entry.content)}
+${entry.messageUrl}`
)
.catch(() => false);
void entry.update({ notified: true });
diff --git a/src/tasks/stats/guildCount.ts b/src/tasks/stats/guildCount.ts
index 262f00c..f52dc95 100644
--- a/src/tasks/stats/guildCount.ts
+++ b/src/tasks/stats/guildCount.ts
@@ -1,5 +1,5 @@
import { BushTask, Time } from '#lib';
-import { GuildCount } from '../../lib/models/shared/GuildCount.js';
+import { GuildCount } from '../../../lib/models/shared/GuildCount.js';
export default class GuildCountTask extends BushTask {
public constructor() {
diff --git a/src/tsconfig.json b/src/tsconfig.json
new file mode 100644
index 0000000..6d2834a
--- /dev/null
+++ b/src/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "../../dist/src",
+ "composite": true
+ },
+ "references": [{ "path": "../lib" }, { "path": "../config" }],
+ "include": ["src/**/*.ts"]
+}