aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorIRONM00N <64110067+IRONM00N@users.noreply.github.com>2021-10-21 00:05:53 -0400
committerIRONM00N <64110067+IRONM00N@users.noreply.github.com>2021-10-21 00:05:53 -0400
commit166d7fdf24440db71311c2cda95697c06e7b8b36 (patch)
tree23b0400362b5f3035b156200eb634d202aa54741 /src/lib
parent08f33f7d450c8920afc3b9fb8886729547065313 (diff)
downloadtanzanite-166d7fdf24440db71311c2cda95697c06e7b8b36.tar.gz
tanzanite-166d7fdf24440db71311c2cda95697c06e7b8b36.tar.bz2
tanzanite-166d7fdf24440db71311c2cda95697c06e7b8b36.zip
Refactoring, rewrote ButtonPaginator, better permission handling + support for send messages in threads, optimizations, another scam link
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/badlinks.ts1
-rw-r--r--src/lib/badwords.ts2
-rw-r--r--src/lib/common/ButtonPaginator.ts186
-rw-r--r--src/lib/common/DeleteButton.ts61
-rw-r--r--src/lib/common/Format.ts72
-rw-r--r--src/lib/common/autoMod.ts52
-rw-r--r--src/lib/common/moderation.ts19
-rw-r--r--src/lib/common/typings/BushInspectOptions.d.ts91
-rw-r--r--src/lib/common/typings/CodeBlockLang.d.ts310
-rw-r--r--src/lib/common/util/Arg.ts120
-rw-r--r--src/lib/extensions/discord-akairo/BushClientUtil.ts856
-rw-r--r--src/lib/extensions/discord-akairo/BushCommand.ts4
-rw-r--r--src/lib/extensions/discord.js/BushGuild.ts2
-rw-r--r--src/lib/extensions/discord.js/BushGuildMember.ts2
-rw-r--r--src/lib/models/Guild.ts2
-rw-r--r--src/lib/utils/Config.ts72
16 files changed, 1031 insertions, 821 deletions
diff --git a/src/lib/badlinks.ts b/src/lib/badlinks.ts
index 933fce3..059d3d9 100644
--- a/src/lib/badlinks.ts
+++ b/src/lib/badlinks.ts
@@ -792,6 +792,7 @@ export default [
"discordcreators.net",
"discordd.buzz",
"discordd.gg",
+ "discordd.gift",
"discorddaapp.com",
"discorddev.com",
"discorddiscord.com",
diff --git a/src/lib/badwords.ts b/src/lib/badwords.ts
index 975df24..e5033d7 100644
--- a/src/lib/badwords.ts
+++ b/src/lib/badwords.ts
@@ -1,4 +1,4 @@
-import { BadWords, Severity } from "./common/autoMod";
+import { BadWords, Severity } from "./common/AutoMod";
export default {
/* -------------------------------------------------------------------------- */
diff --git a/src/lib/common/ButtonPaginator.ts b/src/lib/common/ButtonPaginator.ts
new file mode 100644
index 0000000..c74f6ad
--- /dev/null
+++ b/src/lib/common/ButtonPaginator.ts
@@ -0,0 +1,186 @@
+import {
+ Constants,
+ MessageActionRow,
+ MessageButton,
+ MessageComponentInteraction,
+ MessageEmbed,
+ MessageEmbedOptions
+} from 'discord.js';
+import { BushMessage, BushSlashMessage } from '..';
+import { DeleteButton } from './DeleteButton';
+
+export class ButtonPaginator {
+ protected message: BushMessage | BushSlashMessage;
+ protected embeds: MessageEmbed[] | MessageEmbedOptions[];
+ protected text: string | null;
+ protected deleteOnExit: boolean;
+ protected curPage: number;
+ protected sentMessage: BushMessage | undefined;
+
+ /**
+ * 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: BushMessage | BushSlashMessage,
+ embeds: MessageEmbed[] | MessageEmbedOptions[],
+ text: string | null = null,
+ deleteOnExit = true,
+ startOn = 1
+ ): Promise<void> {
+ // 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();
+ }
+
+ protected get numPages(): number {
+ return this.embeds.length;
+ }
+
+ protected constructor(
+ message: BushMessage | BushSlashMessage,
+ embeds: MessageEmbed[] | MessageEmbedOptions[],
+ text: string | null,
+ deleteOnExit: boolean,
+ startOn: number
+ ) {
+ this.message = message;
+ this.embeds = embeds;
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ this.text = text || null;
+ this.deleteOnExit = deleteOnExit;
+ this.curPage = startOn - 1;
+
+ // add footers
+ for (let i = 0; i < embeds.length; i++) {
+ if (embeds[i] instanceof MessageEmbed) {
+ (embeds[i] as MessageEmbed).setFooter(`Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}`);
+ } else {
+ (embeds[i] as MessageEmbedOptions).footer = {
+ text: `Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}`
+ };
+ }
+ }
+ }
+
+ protected async send() {
+ this.sentMessage = (await this.message.util.reply({
+ content: this.text,
+ embeds: [this.embeds[this.curPage]],
+ components: [this.getPaginationRow()]
+ })) as BushMessage;
+
+ const collector = this.sentMessage.createMessageComponentCollector({
+ filter: (i) => i.customId.startsWith('paginate_') && i.message.id === this.sentMessage!.id,
+ time: 300000
+ });
+
+ collector.on('collect', (i) => void this.collect(i));
+ collector.on('end', () => void this.end());
+ }
+
+ protected async collect(interaction: MessageComponentInteraction) {
+ if (interaction.user.id !== this.message.author.id || !client.config.owners.includes(interaction.user.id))
+ return await interaction?.deferUpdate().catch(() => undefined);
+
+ switch (interaction.customId) {
+ case 'paginate_beginning':
+ this.curPage = 0;
+ return this.edit(interaction);
+ case 'paginate_back':
+ this.curPage--;
+ return await this.edit(interaction);
+ case 'paginate_stop':
+ if (this.deleteOnExit) {
+ await interaction.deferUpdate().catch(() => undefined);
+ return await this.sentMessage!.delete().catch(() => undefined);
+ } else {
+ return await interaction
+ ?.update({
+ content: `${this.text ? `${this.text}\n` : ''}Command closed by user.`,
+ embeds: [],
+ components: []
+ })
+ .catch(() => undefined);
+ }
+ case 'paginate_next':
+ this.curPage++;
+ return await this.edit(interaction);
+ case 'paginate_end':
+ this.curPage = this.embeds.length - 1;
+ return await this.edit(interaction);
+ }
+ }
+
+ protected async end() {
+ try {
+ return this.sentMessage!.edit({
+ content: this.text,
+ embeds: [this.embeds[this.curPage]],
+ components: [this.getPaginationRow(true)]
+ });
+ } catch (e) {
+ return undefined;
+ }
+ }
+
+ protected async edit(interaction: MessageComponentInteraction) {
+ try {
+ return interaction?.update({
+ content: this.text,
+ embeds: [this.embeds[this.curPage]],
+ components: [this.getPaginationRow()]
+ });
+ } catch (e) {
+ return undefined;
+ }
+ }
+
+ protected getPaginationRow(disableAll = false): MessageActionRow {
+ return new MessageActionRow().addComponents(
+ new MessageButton({
+ style: Constants.MessageButtonStyles.PRIMARY,
+ customId: 'paginate_beginning',
+ emoji: PaginateEmojis.BEGGING,
+ disabled: disableAll || this.curPage === 0
+ }),
+ new MessageButton({
+ style: Constants.MessageButtonStyles.PRIMARY,
+ customId: 'paginate_back',
+ emoji: PaginateEmojis.BACK,
+ disabled: disableAll || this.curPage === 0
+ }),
+ new MessageButton({
+ style: Constants.MessageButtonStyles.PRIMARY,
+ customId: 'paginate_stop',
+ emoji: PaginateEmojis.STOP,
+ disabled: disableAll
+ }),
+ new MessageButton({
+ style: Constants.MessageButtonStyles.PRIMARY,
+ customId: 'paginate_next',
+ emoji: PaginateEmojis.FORWARD,
+ disabled: disableAll || this.curPage === this.numPages - 1
+ }),
+ new MessageButton({
+ style: Constants.MessageButtonStyles.PRIMARY,
+ customId: 'paginate_end',
+ emoji: PaginateEmojis.END,
+ disabled: disableAll || this.curPage === this.numPages - 1
+ })
+ );
+ }
+}
+
+export const enum PaginateEmojis {
+ BEGGING = '853667381335162910',
+ BACK = '853667410203770881',
+ STOP = '853667471110570034',
+ FORWARD = '853667492680564747',
+ END = '853667514915225640'
+}
diff --git a/src/lib/common/DeleteButton.ts b/src/lib/common/DeleteButton.ts
new file mode 100644
index 0000000..7d2e41b
--- /dev/null
+++ b/src/lib/common/DeleteButton.ts
@@ -0,0 +1,61 @@
+import { Constants, MessageActionRow, MessageButton, MessageComponentInteraction, MessageOptions } from 'discord.js';
+import { BushMessage, BushSlashMessage } from '..';
+import { PaginateEmojis } from './ButtonPaginator';
+
+export class DeleteButton {
+ protected messageOptions: MessageOptions;
+ protected message: BushMessage | BushSlashMessage;
+
+ /**
+ * 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
+ */
+ static async send(message: BushMessage | BushSlashMessage, options: Omit<MessageOptions, 'components'>) {
+ return new DeleteButton(message, options).send();
+ }
+
+ protected constructor(message: BushMessage | BushSlashMessage, options: MessageOptions) {
+ this.message = message;
+ this.messageOptions = options;
+ }
+
+ protected async send() {
+ this.updateComponents();
+
+ const msg = (await this.message.util.reply(this.messageOptions)) as BushMessage;
+
+ 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 || client.config.owners.includes(interaction.user.id)) {
+ if (msg.deletable && !msg.deleted) await msg.delete();
+ }
+ });
+
+ collector.on('end', async () => {
+ this.updateComponents(true, true);
+ await msg.edit(this.messageOptions).catch(() => undefined);
+ });
+ }
+
+ protected updateComponents(edit = false, disable = false): void {
+ this.messageOptions.components = [
+ new MessageActionRow().addComponents(
+ new MessageButton({
+ style: Constants.MessageButtonStyles.PRIMARY,
+ customId: 'paginate__stop',
+ emoji: PaginateEmojis.STOP,
+ disabled: disable
+ })
+ )
+ ];
+ if (edit) {
+ this.messageOptions.reply = undefined;
+ }
+ }
+}
diff --git a/src/lib/common/Format.ts b/src/lib/common/Format.ts
new file mode 100644
index 0000000..ba1ee9f
--- /dev/null
+++ b/src/lib/common/Format.ts
@@ -0,0 +1,72 @@
+import { Formatters, Util } from 'discord.js';
+import { CodeBlockLang } from './typings/CodeBlockLang';
+
+/**
+ * Formats and escapes content for formatting
+ */
+export class Format {
+ /**
+ * Wraps the content inside a codeblock with no language.
+ * @param content The content to wrap.
+ */
+ public static 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.
+ */
+ public static codeBlock(language: CodeBlockLang, content: string): string;
+ public static codeBlock(languageOrContent: string, content?: string): string {
+ return typeof content === 'undefined'
+ ? Formatters.codeBlock(Util.escapeCodeBlock(languageOrContent))
+ : Formatters.codeBlock(languageOrContent, Util.escapeCodeBlock(languageOrContent));
+ }
+
+ /**
+ * Wraps the content inside \`backticks\`, which formats it as inline code.
+ * @param content The content to wrap.
+ */
+ public static inlineCode(content: string): string {
+ return Formatters.inlineCode(Util.escapeInlineCode(content));
+ }
+
+ /**
+ * Formats the content into italic text.
+ * @param content The content to wrap.
+ */
+ public static italic(content: string): string {
+ return Formatters.italic(Util.escapeItalic(content));
+ }
+
+ /**
+ * Formats the content into bold text.
+ * @param content The content to wrap.
+ */
+ public static bold(content: string): string {
+ return Formatters.bold(Util.escapeBold(content));
+ }
+
+ /**
+ * Formats the content into underscored text.
+ * @param content The content to wrap.
+ */
+ public static underscore(content: string): string {
+ return Formatters.underscore(Util.escapeUnderline(content));
+ }
+
+ /**
+ * Formats the content into strike-through text.
+ * @param content The content to wrap.
+ */
+ public static strikethrough(content: string): string {
+ return Formatters.strikethrough(Util.escapeStrikethrough(content));
+ }
+
+ /**
+ * Wraps the content inside spoiler (hidden text).
+ * @param content The content to wrap.
+ */
+ public static spoiler(content: string): string {
+ return Formatters.spoiler(Util.escapeSpoiler(content));
+ }
+}
diff --git a/src/lib/common/autoMod.ts b/src/lib/common/autoMod.ts
index 0bdbebf..312beb3 100644
--- a/src/lib/common/autoMod.ts
+++ b/src/lib/common/autoMod.ts
@@ -1,17 +1,17 @@
-import { Formatters, MessageActionRow, MessageButton, MessageEmbed, TextChannel } from 'discord.js';
-import badLinksArray from '../../lib/badlinks';
-import badLinksSecretArray from '../../lib/badlinks-secret'; // I cannot make this public so just make a new file that export defaults an empty array
-import badWords from '../../lib/badwords';
+import { GuildMember, MessageActionRow, MessageButton, MessageEmbed, TextChannel } from 'discord.js';
+import badLinksArray from '../badlinks';
+import badLinksSecretArray from '../badlinks-secret'; // I cannot make this public so just make a new file that export defaults an empty array
+import badWords from '../badwords';
import { BushButtonInteraction } from '../extensions/discord.js/BushButtonInteraction';
-import { BushGuildMember } from '../extensions/discord.js/BushGuildMember';
import { BushMessage } from '../extensions/discord.js/BushMessage';
-import { Moderation } from './moderation';
+import { Moderation } from './Moderation';
export class AutoMod {
private message: BushMessage;
public constructor(message: BushMessage) {
this.message = message;
+ if (message.author.id === client.user?.id) return;
void this.handle();
}
@@ -21,17 +21,10 @@ export class AutoMod {
const customAutomodPhrases = (await this.message.guild.getSetting('autoModPhases')) ?? {};
const badLinks: BadWords = {};
- const badLinksSecret: BadWords = {};
- badLinksArray.forEach((link) => {
- badLinks[link] = {
- severity: Severity.PERM_MUTE,
- ignoreSpaces: true,
- ignoreCapitalization: true,
- reason: 'malicious link'
- };
- });
- badLinksSecretArray.forEach((link) => {
+ const uniqueLinks = [...new Set([...badLinksArray, ...badLinksSecretArray])];
+
+ uniqueLinks.forEach((link) => {
badLinks[link] = {
severity: Severity.PERM_MUTE,
ignoreSpaces: true,
@@ -43,9 +36,7 @@ export class AutoMod {
const result = {
...this.checkWords(customAutomodPhrases),
...this.checkWords((await this.message.guild.hasFeature('excludeDefaultAutomod')) ? {} : badWords),
- ...this.checkWords(
- (await this.message.guild.hasFeature('excludeAutomodScamLinks')) ? {} : { ...badLinks, ...badLinksSecret }
- )
+ ...this.checkWords((await this.message.guild.hasFeature('excludeAutomodScamLinks')) ? {} : badLinks)
};
if (Object.keys(result).length === 0) return;
@@ -59,9 +50,7 @@ export class AutoMod {
embeds: [
{
title: 'AutoMod Error',
- description: `Unable to find severity information for ${Formatters.inlineCode(
- util.discord.escapeInlineCode(highestOffence.word)
- )}`,
+ description: `Unable to find severity information for ${util.format.inlineCode(highestOffence.word)}`,
color: util.colors.error
}
]
@@ -128,7 +117,7 @@ export class AutoMod {
break;
}
default: {
- throw new Error('Invalid severity');
+ throw new Error(`Invalid severity: ${highestOffence.severity}`);
}
}
@@ -163,8 +152,8 @@ export class AutoMod {
.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:** ${util
- .surroundArray(Object.keys(offences), '`')
+ }> [Jump to context](${this.message.url})\n**Blacklisted Words:** ${Object.keys(offences)
+ .map((key) => `\`${key}\``)
.join(', ')}`
)
.addField('Message Content', `${await util.codeblock(this.message.content, 1024)}`)
@@ -194,12 +183,13 @@ export class AutoMod {
const [action, userId, reason] = interaction.customId.replace('automod;', '').split(';');
switch (action) {
case 'ban': {
- const check = await Moderation.permissionCheck(
- interaction.member as BushGuildMember,
- interaction.guild!.members.cache.get(userId)!,
- 'ban',
- true
- );
+ const victim = await interaction.guild!.members.fetch(userId);
+ const moderator =
+ interaction.member instanceof GuildMember
+ ? interaction.member
+ : await interaction.guild!.members.fetch(interaction.user.id);
+
+ const check = victim ? await Moderation.permissionCheck(moderator, victim, 'ban', true) : true;
if (check !== true)
return interaction.reply({
diff --git a/src/lib/common/moderation.ts b/src/lib/common/moderation.ts
index c8779fc..29d66fa 100644
--- a/src/lib/common/moderation.ts
+++ b/src/lib/common/moderation.ts
@@ -123,7 +123,7 @@ export class Moderation {
const expires = options.duration ? new Date(+new Date() + options.duration ?? 0) : undefined;
const user = (await util.resolveNonCachedUser(options.user))!.id;
const guild = client.guilds.resolveId(options.guild)!;
- const type = this.#findTypeEnum(options.type)!;
+ const type = this.findTypeEnum(options.type)!;
const entry = ActivePunishment.build(
options.extraInfo
@@ -144,7 +144,7 @@ export class Moderation {
}): Promise<boolean> {
const user = await util.resolveNonCachedUser(options.user);
const guild = client.guilds.resolveId(options.guild);
- const type = this.#findTypeEnum(options.type);
+ const type = this.findTypeEnum(options.type);
if (!user || !guild) return false;
@@ -160,18 +160,19 @@ export class Moderation {
success = false;
});
if (entries) {
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
- entries.forEach(async (entry) => {
- await entry.destroy().catch(async (e) => {
+ const promises = entries.map(async (entry) =>
+ entry.destroy().catch(async (e) => {
await util.handleError('removePunishmentEntry', e);
- });
- success = false;
- });
+ success = false;
+ })
+ );
+
+ await Promise.all(promises);
}
return success;
}
- static #findTypeEnum(type: 'mute' | 'ban' | 'role' | 'block') {
+ private static findTypeEnum(type: 'mute' | 'ban' | 'role' | 'block') {
const typeMap = {
['mute']: ActivePunishmentType.MUTE,
['ban']: ActivePunishmentType.BAN,
diff --git a/src/lib/common/typings/BushInspectOptions.d.ts b/src/lib/common/typings/BushInspectOptions.d.ts
new file mode 100644
index 0000000..c2a2360
--- /dev/null
+++ b/src/lib/common/typings/BushInspectOptions.d.ts
@@ -0,0 +1,91 @@
+import { InspectOptions } from 'util';
+
+/**
+ * {@link https://nodejs.org/api/util.html#util_util_inspect_object_options}
+ */
+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;
+}
diff --git a/src/lib/common/typings/CodeBlockLang.d.ts b/src/lib/common/typings/CodeBlockLang.d.ts
new file mode 100644
index 0000000..5a1aeba
--- /dev/null
+++ b/src/lib/common/typings/CodeBlockLang.d.ts
@@ -0,0 +1,310 @@
+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';
diff --git a/src/lib/common/util/Arg.ts b/src/lib/common/util/Arg.ts
new file mode 100644
index 0000000..84d5aeb
--- /dev/null
+++ b/src/lib/common/util/Arg.ts
@@ -0,0 +1,120 @@
+import { Argument, ArgumentTypeCaster, Flag, ParsedValuePredicate, TypeResolver } from 'discord-akairo';
+import { Message } from 'discord.js';
+import { BushArgumentType } from '../..';
+
+export class Arg {
+ /**
+ * Casts a phrase to this argument's type.
+ * @param type - The type to cast to.
+ * @param resolver - The type resolver.
+ * @param message - Message that called the command.
+ * @param phrase - Phrase to process.
+ */
+ public static cast(type: BushArgumentType, resolver: TypeResolver, message: Message, phrase: string): Promise<any> {
+ return Argument.cast(type, resolver, 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.
+ */
+ public static compose(...types: BushArgumentType[]): ArgumentTypeCaster {
+ return Argument.compose(...types);
+ }
+
+ /**
+ * 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.
+ */
+ public static composeWithFailure(...types: BushArgumentType[]): ArgumentTypeCaster {
+ return Argument.composeWithFailure(...types);
+ }
+
+ /**
+ * Checks if something is null, undefined, or a fail flag.
+ * @param value - Value to check.
+ */
+ public static 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.
+ */
+ public static product(...types: BushArgumentType[]): ArgumentTypeCaster {
+ return Argument.product(...types);
+ }
+
+ /**
+ * 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.
+ */
+ public static range(type: BushArgumentType, min: number, max: number, inclusive?: boolean): ArgumentTypeCaster {
+ return Argument.range(type, 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.
+ */
+ public static tagged(type: BushArgumentType, tag?: any): ArgumentTypeCaster {
+ return Argument.tagged(type, 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.
+ */
+ public static taggedUnion(...types: BushArgumentType[]): ArgumentTypeCaster {
+ return Argument.taggedUnion(...types);
+ }
+
+ /**
+ * 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.
+ */
+ public static taggedWithInput(type: BushArgumentType, tag?: any): ArgumentTypeCaster {
+ return Argument.taggedWithInput(type, 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.
+ */
+ public static union(...types: BushArgumentType[]): ArgumentTypeCaster {
+ return Argument.union(...types);
+ }
+
+ /**
+ * 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.
+ */
+ public static validate(type: BushArgumentType, predicate: ParsedValuePredicate): ArgumentTypeCaster {
+ return Argument.validate(type, 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.
+ */
+ public static withInput(type: BushArgumentType): ArgumentTypeCaster {
+ return Argument.withInput(type);
+ }
+}
diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts
index 448eaf3..499d2c7 100644
--- a/src/lib/extensions/discord-akairo/BushClientUtil.ts
+++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts
@@ -1,5 +1,4 @@
import {
- BushArgumentType,
BushCache,
BushClient,
BushConstants,
@@ -11,30 +10,16 @@ import {
PronounCode
} from '@lib';
import { exec } from 'child_process';
-import {
- Argument,
- ArgumentTypeCaster,
- ClientUtil,
- Flag,
- ParsedValuePredicate,
- TypeResolver,
- Util as AkairoUtil
-} from 'discord-akairo';
+import { ClientUtil, Util as AkairoUtil } from 'discord-akairo';
import { APIMessage } from 'discord-api-types';
import {
ColorResolvable,
CommandInteraction,
- Constants,
GuildMember,
InteractionReplyOptions,
Message,
- MessageActionRow,
- MessageButton,
- MessageComponentInteraction,
- MessageEditOptions,
MessageEmbed,
- MessageEmbedOptions,
- MessageOptions,
+ PermissionResolvable,
Snowflake,
TextChannel,
ThreadMember,
@@ -46,443 +31,16 @@ import got from 'got';
import humanizeDuration from 'humanize-duration';
import _ from 'lodash';
import moment from 'moment';
-import { inspect, InspectOptions, promisify } from 'util';
+import { inspect, promisify } from 'util';
import CommandErrorListener from '../../../listeners/commands/commandError';
+import { Format } from '../../common/Format';
+import { BushInspectOptions } from '../../common/typings/BushInspectOptions';
+import { CodeBlockLang } from '../../common/typings/CodeBlockLang';
+import { Arg } from '../../common/util/Arg';
import { BushNewsChannel } from '../discord.js/BushNewsChannel';
import { BushTextChannel } from '../discord.js/BushTextChannel';
import { BushSlashEditMessageType, BushSlashSendMessageType, BushUserResolvable } from './BushClient';
-interface hastebinRes {
- key: string;
-}
-
-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;
-}
-
-interface MojangProfile {
- username: string;
- uuid: string;
-}
-
-// #region codeblock type
-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';
-//#endregion
-
-/**
- * {@link https://nodejs.org/api/util.html#util_util_inspect_object_options}
- */
-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;
-}
-
export class BushClientUtil extends ClientUtil {
/**
* The client.
@@ -504,22 +62,6 @@ export class BushClientUtil extends ClientUtil {
];
/**
- * Emojis used for {@link BushClientUtil.buttonPaginate}
- */
- #paginateEmojis = {
- beginning: '853667381335162910',
- back: '853667410203770881',
- stop: '853667471110570034',
- forward: '853667492680564747',
- end: '853667514915225640'
- };
-
- /**
- * A simple promise exec method
- */
- #exec = promisify(exec);
-
- /**
* Creates this client util
* @param client The client to initialize with
*/
@@ -554,7 +96,7 @@ export class BushClientUtil extends ClientUtil {
stdout: string;
stderr: string;
}> {
- return await this.#exec(command);
+ return await promisify(exec)(command);
}
/**
@@ -677,193 +219,13 @@ export class BushClientUtil extends ClientUtil {
}
/**
- * Paginates an array of embeds using buttons.
- */
- public async buttonPaginate(
- message: BushMessage | BushSlashMessage,
- embeds: MessageEmbed[] | MessageEmbedOptions[],
- text: string | null = null,
- deleteOnExit?: boolean,
- startOn?: number
- ): Promise<void> {
- const paginateEmojis = this.#paginateEmojis;
- if (deleteOnExit === undefined) deleteOnExit = true;
-
- if (embeds.length === 1) {
- return this.sendWithDeleteButton(message, { embeds: embeds });
- }
-
- embeds.forEach((_e, i) => {
- embeds[i] instanceof MessageEmbed
- ? (embeds[i] as MessageEmbed).setFooter(`Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}`)
- : ((embeds[i] as MessageEmbedOptions).footer = {
- text: `Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}`
- });
- });
-
- const style = Constants.MessageButtonStyles.PRIMARY;
- let curPage = startOn ? startOn - 1 : 0;
- if (typeof embeds !== 'object') throw new Error('embeds must be an object');
- const msg = (await message.util.reply({
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- content: text || null,
- embeds: [embeds[curPage]],
- components: [getPaginationRow()]
- })) as Message;
- const filter = (interaction: MessageComponentInteraction) =>
- interaction.customId.startsWith('paginate_') && interaction.message.id === msg.id;
- const collector = msg.createMessageComponentCollector({ filter, time: 300000 });
- collector.on('collect', async (interaction: MessageComponentInteraction) => {
- if (interaction.user.id === message.author.id || client.config.owners.includes(interaction.user.id)) {
- switch (interaction.customId) {
- case 'paginate_beginning': {
- curPage = 0;
- await edit(interaction);
- break;
- }
- case 'paginate_back': {
- curPage--;
- await edit(interaction);
- break;
- }
- case 'paginate_stop': {
- if (deleteOnExit) {
- await interaction.deferUpdate().catch(() => undefined);
- if (msg.deletable && !msg.deleted) {
- await msg.delete();
- }
- } else {
- await interaction
- ?.update({
- content: `${text ? `${text}\n` : ''}Command closed by user.`,
- embeds: [],
- components: []
- })
- .catch(() => undefined);
- }
- return;
- }
- case 'paginate_next': {
- curPage++;
- await edit(interaction);
- break;
- }
- case 'paginate_end': {
- curPage = embeds.length - 1;
- await edit(interaction);
- break;
- }
- }
- } else {
- return await interaction?.deferUpdate().catch(() => undefined);
- }
- });
-
- collector.on('end', async () => {
- await msg
- .edit({
- content: text,
- embeds: [embeds[curPage]],
- components: [getPaginationRow(true)]
- })
- .catch(() => undefined);
- });
-
- async function edit(interaction: MessageComponentInteraction): Promise<void> {
- return await interaction
- ?.update({ content: text, embeds: [embeds[curPage]], components: [getPaginationRow()] })
- .catch(() => undefined);
- }
- function getPaginationRow(disableAll = false): MessageActionRow {
- return new MessageActionRow().addComponents(
- new MessageButton({
- style,
- customId: 'paginate_beginning',
- emoji: paginateEmojis.beginning,
- disabled: disableAll || curPage === 0
- }),
- new MessageButton({
- style,
- customId: 'paginate_back',
- emoji: paginateEmojis.back,
- disabled: disableAll || curPage === 0
- }),
- new MessageButton({
- style,
- customId: 'paginate_stop',
- emoji: paginateEmojis.stop,
- disabled: disableAll
- }),
- new MessageButton({
- style,
- customId: 'paginate_next',
- emoji: paginateEmojis.forward,
- disabled: disableAll || curPage === embeds.length - 1
- }),
- new MessageButton({
- style,
- customId: 'paginate_end',
- emoji: paginateEmojis.end,
- disabled: disableAll || curPage === embeds.length - 1
- })
- );
- }
- }
-
- /**
- * Sends a message with a button for the user to delete it.
- */
- public async sendWithDeleteButton(message: BushMessage | BushSlashMessage, options: MessageOptions): Promise<void> {
- const paginateEmojis = this.#paginateEmojis;
- updateOptions();
- const msg = (await message.util.reply(options as MessageOptions & { split?: false })) as Message;
- const filter = (interaction: MessageComponentInteraction) =>
- interaction.customId == 'paginate__stop' && interaction.message == msg;
- const collector = msg.createMessageComponentCollector({ filter, time: 300000 });
- collector.on('collect', async (interaction: MessageComponentInteraction) => {
- if (interaction.user.id == message.author.id || client.config.owners.includes(interaction.user.id)) {
- await interaction.deferUpdate().catch(() => undefined);
- if (msg.deletable && !msg.deleted) {
- await msg.delete();
- }
- return;
- } else {
- return await interaction?.deferUpdate().catch(() => undefined);
- }
- });
-
- collector.on('end', async () => {
- updateOptions(true, true);
- await msg.edit(options as MessageEditOptions).catch(() => undefined);
- });
-
- function updateOptions(edit?: boolean, disable?: boolean) {
- if (edit == undefined) edit = false;
- if (disable == undefined) disable = false;
- options.components = [
- new MessageActionRow().addComponents(
- new MessageButton({
- style: Constants.MessageButtonStyles.PRIMARY,
- customId: 'paginate__stop',
- emoji: paginateEmojis.stop,
- disabled: disable
- })
- )
- ];
- if (edit) {
- options.reply = undefined;
- }
- }
- }
-
- /**
* 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
*/
public async codeblock(code: string, length: number, language?: CodeBlockLang, substr = false): Promise<string> {
let hasteOut = '';
- code = util.discord.escapeCodeBlock(code);
+ code = this.discord.escapeCodeBlock(code);
const prefix = `\`\`\`${language}\n`;
const suffix = '\n```';
language = language ?? 'txt';
@@ -887,7 +249,12 @@ export class BushClientUtil extends ClientUtil {
return code3;
}
- public inspect(code: any, options?: BushInspectOptions): string {
+ /**
+ * 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
+ */
+ public inspect(object: any, options?: BushInspectOptions): string {
const {
showHidden: _showHidden = false,
depth: _depth = 2,
@@ -914,7 +281,7 @@ export class BushClientUtil extends ClientUtil {
sorted: _sorted,
getters: _getters
};
- return inspect(code, optionsWithDefaults);
+ return inspect(object, optionsWithDefaults);
}
#mapCredential(old: string): string {
@@ -931,6 +298,7 @@ export class BushClientUtil extends ClientUtil {
/**
* Redacts credentials from a string
+ * @param text - The text to redact credentials from
*/
public redact(text: string) {
for (const credentialName in { ...client.config.credentials, dbPassword: client.config.db.password }) {
@@ -1028,7 +396,7 @@ export class BushClientUtil extends ClientUtil {
const newValue = this.addOrRemoveFromArray(action, oldValue, value);
row[key] = newValue;
client.cache.global[key] = newValue;
- return await row.save().catch((e) => util.handleError('insertOrRemoveFromGlobal', e));
+ return await row.save().catch((e) => this.handleError('insertOrRemoveFromGlobal', e));
}
/**
@@ -1233,136 +601,108 @@ export class BushClientUtil extends ClientUtil {
return str.replace(/[\u0000-\u001F\u007F-\u009F\u200B]/g, '');
}
- get arg() {
- return class Arg {
- /**
- * Casts a phrase to this argument's type.
- * @param type - The type to cast to.
- * @param resolver - The type resolver.
- * @param message - Message that called the command.
- * @param phrase - Phrase to process.
- */
- public static cast(type: BushArgumentType, resolver: TypeResolver, message: Message, phrase: string): Promise<any> {
- return Argument.cast(type, resolver, message, phrase);
- }
+ public async uploadImageToImgur(image: string) {
+ const clientId = this.client.config.credentials.imgurClientId;
- /**
- * 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.
- */
- public static compose(...types: BushArgumentType[]): ArgumentTypeCaster {
- return Argument.compose(...types);
- }
+ const resp = (await got
+ .post('https://api.imgur.com/3/upload', {
+ headers: {
+ // Authorization: `Bearer ${token}`,
+ Authorization: `Client-ID ${clientId}`,
+ Accept: 'application/json'
+ },
+ form: {
+ image: image,
+ type: 'base64'
+ },
+ followRedirect: true
+ })
+ .json()) as { data: { link: string } };
- /**
- * 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.
- */
- public static composeWithFailure(...types: BushArgumentType[]): ArgumentTypeCaster {
- return Argument.composeWithFailure(...types);
- }
+ return resp.data.link;
+ }
- /**
- * Checks if something is null, undefined, or a fail flag.
- * @param value - Value to check.
- */
- public static isFailure(value: any): value is null | undefined | (Flag & { value: any }) {
- return Argument.isFailure(value);
- }
+ public userGuildPermCheck(message: BushMessage | BushSlashMessage, permissions: PermissionResolvable) {
+ const missing = message.member?.permissions.missing(permissions) ?? [];
- /**
- * 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.
- */
- public static product(...types: BushArgumentType[]): ArgumentTypeCaster {
- return Argument.product(...types);
- }
+ return missing.length ? missing : null;
+ }
- /**
- * 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.
- */
- public static range(type: BushArgumentType, min: number, max: number, inclusive?: boolean): ArgumentTypeCaster {
- return Argument.range(type, min, max, inclusive);
- }
+ public clientGuildPermCheck(message: BushMessage | BushSlashMessage, permissions: PermissionResolvable) {
+ const missing = message.guild?.me?.permissions.missing(permissions) ?? [];
- /**
- * 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.
- */
- public static tagged(type: BushArgumentType, tag?: any): ArgumentTypeCaster {
- return Argument.tagged(type, tag);
- }
+ return missing.length ? missing : null;
+ }
- /**
- * 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.
- */
- public static taggedUnion(...types: BushArgumentType[]): ArgumentTypeCaster {
- return Argument.taggedUnion(...types);
- }
+ public clientSendAndPermCheck(
+ message: BushMessage | BushSlashMessage,
+ permissions: PermissionResolvable = [],
+ checkChannel = false
+ ) {
+ const missing = [];
+ const sendPerm = message.channel!.isThread() ? 'SEND_MESSAGES' : 'SEND_MESSAGES_IN_THREADS';
- /**
- * 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.
- */
- public static taggedWithInput(type: BushArgumentType, tag?: any): ArgumentTypeCaster {
- return Argument.taggedWithInput(type, tag);
- }
+ if (!message.guild!.me!.permissionsIn(message.channel!.id!).has(sendPerm)) missing.push(sendPerm);
- /**
- * 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.
- */
- public static union(...types: BushArgumentType[]): ArgumentTypeCaster {
- return Argument.union(...types);
- }
+ missing.push(
+ ...(checkChannel
+ ? message.guild!.me!.permissionsIn(message.channel!.id!).missing(permissions)
+ : this.clientGuildPermCheck(message, permissions) ?? [])
+ );
- /**
- * 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.
- */
- public static validate(type: BushArgumentType, predicate: ParsedValuePredicate): ArgumentTypeCaster {
- return Argument.validate(type, predicate);
- }
+ return missing.length ? missing : null;
+ }
- /**
- * 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.
- */
- public static withInput(type: BushArgumentType): ArgumentTypeCaster {
- return Argument.withInput(type);
- }
- };
+ public get arg() {
+ return Arg;
+ }
+
+ /**
+ * Formats and escapes content for formatting
+ */
+ public get format() {
+ return Format;
}
/**
* Discord.js's Util class
*/
- get discord() {
+ public get discord() {
return DiscordUtil;
}
/**
* discord-akairo's Util class
*/
- get akairo() {
+ public get akairo() {
return AkairoUtil;
}
}
+
+interface hastebinRes {
+ key: string;
+}
+
+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;
+}
+
+interface MojangProfile {
+ username: string;
+ uuid: string;
+}
diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts
index 0be128b..22d4aae 100644
--- a/src/lib/extensions/discord-akairo/BushCommand.ts
+++ b/src/lib/extensions/discord-akairo/BushCommand.ts
@@ -158,9 +158,9 @@ export interface BushCommandOptions extends Omit<CommandOptions, 'userPermission
/** Allow this command to be run in channels that are blacklisted. */
bypassChannelBlacklist?: boolean;
/** Permissions required by the client to run this command. */
- clientPermissions?: PermissionResolvable | PermissionResolvable[] | BushMissingPermissionSupplier;
+ clientPermissions: PermissionResolvable | PermissionResolvable[] | BushMissingPermissionSupplier;
/** Permissions required by the user to run this command. */
- userPermissions?: PermissionResolvable | PermissionResolvable[] | BushMissingPermissionSupplier;
+ userPermissions: PermissionResolvable | PermissionResolvable[] | BushMissingPermissionSupplier;
}
export class BushCommand extends Command {
diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts
index 8d44cd4..51c2795 100644
--- a/src/lib/extensions/discord.js/BushGuild.ts
+++ b/src/lib/extensions/discord.js/BushGuild.ts
@@ -1,6 +1,6 @@
import { Guild, MessageOptions, UserResolvable } from 'discord.js';
import { RawGuildData } from 'discord.js/typings/rawDataTypes';
-import { Moderation } from '../../common/moderation';
+import { Moderation } from '../../common/Moderation';
import { Guild as GuildDB, GuildFeatures, GuildLogType, GuildModel } from '../../models/Guild';
import { ModLogType } from '../../models/ModLog';
import { BushClient, BushUserResolvable } from '../discord-akairo/BushClient';
diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts
index 954342d..77d03b1 100644
--- a/src/lib/extensions/discord.js/BushGuildMember.ts
+++ b/src/lib/extensions/discord.js/BushGuildMember.ts
@@ -1,6 +1,6 @@
import { GuildMember, MessageEmbed, Partialize, Role } from 'discord.js';
import { RawGuildMemberData } from 'discord.js/typings/rawDataTypes';
-import { Moderation } from '../../common/moderation';
+import { Moderation } from '../../common/Moderation';
import { ModLogType } from '../../models/ModLog';
import { BushClient } from '../discord-akairo/BushClient';
import { BushGuild } from './BushGuild';
diff --git a/src/lib/models/Guild.ts b/src/lib/models/Guild.ts
index d23bb0f..7b51e61 100644
--- a/src/lib/models/Guild.ts
+++ b/src/lib/models/Guild.ts
@@ -1,6 +1,6 @@
import { Snowflake } from 'discord.js';
import { DataTypes, Sequelize } from 'sequelize';
-import { BadWords } from '../common/autoMod';
+import { BadWords } from '../common/AutoMod';
import { BushClient } from '../extensions/discord-akairo/BushClient';
import { BaseModel } from './BaseModel';
import { jsonArrayInit, jsonParseGet, jsonParseSet, NEVER_USED } from './__helpers';
diff --git a/src/lib/utils/Config.ts b/src/lib/utils/Config.ts
index ed73d40..393dd44 100644
--- a/src/lib/utils/Config.ts
+++ b/src/lib/utils/Config.ts
@@ -1,25 +1,14 @@
import { Snowflake } from 'discord.js';
-export interface ConfigOptions {
- credentials: { token: string; betaToken: string; devToken: string; hypixelApiKey: string; wolframAlphaAppId: string };
- environment: 'production' | 'beta' | 'development';
- owners: Snowflake[];
- prefix: string;
- channels: { log: Snowflake; error: Snowflake; dm: Snowflake };
- db: { host: string; port: number; username: string; password: string };
- logging: { db: boolean; verbose: boolean; info: boolean };
- supportGuild: { id: Snowflake; invite: string };
-}
-
export class Config {
- public credentials: { token: string; betaToken: string; devToken: string; hypixelApiKey: string; wolframAlphaAppId: string };
- public environment: 'production' | 'beta' | 'development';
+ public credentials: Credentials;
+ public environment: Environment;
public owners: Snowflake[];
public prefix: string;
- public channels: { log: Snowflake; error: Snowflake; dm: Snowflake };
- public db: { host: string; port: number; username: string; password: string };
- public logging: { db: boolean; verbose: boolean; info: boolean };
- public supportGuild: { id: Snowflake; invite: string };
+ public channels: Channels;
+ public db: DataBase;
+ public logging: Logging;
+ public supportGuild: SupportGuild;
public constructor(options: ConfigOptions) {
this.credentials = options.credentials;
@@ -43,10 +32,59 @@ export class Config {
public get isProduction(): boolean {
return this.environment === 'production';
}
+
public get isBeta(): boolean {
return this.environment === 'beta';
}
+
public get isDevelopment(): boolean {
return this.environment === 'development';
}
}
+
+export interface ConfigOptions {
+ credentials: Credentials;
+ environment: Environment;
+ owners: Snowflake[];
+ prefix: string;
+ channels: Channels;
+ db: DataBase;
+ logging: Logging;
+ supportGuild: SupportGuild;
+}
+
+interface Credentials {
+ token: string;
+ betaToken: string;
+ devToken: string;
+ hypixelApiKey: string;
+ wolframAlphaAppId: string;
+ imgurClientId: string;
+ imgurClientSecret: string;
+}
+
+type Environment = 'production' | 'beta' | 'development';
+
+interface Channels {
+ log: Snowflake;
+ error: Snowflake;
+ dm: Snowflake;
+}
+
+interface DataBase {
+ host: string;
+ port: number;
+ username: string;
+ password: string;
+}
+
+interface Logging {
+ db: boolean;
+ verbose: boolean;
+ info: boolean;
+}
+
+interface SupportGuild {
+ id: Snowflake;
+ invite: string;
+}