diff options
author | IRONM00N <64110067+IRONM00N@users.noreply.github.com> | 2021-10-21 00:05:53 -0400 |
---|---|---|
committer | IRONM00N <64110067+IRONM00N@users.noreply.github.com> | 2021-10-21 00:05:53 -0400 |
commit | 166d7fdf24440db71311c2cda95697c06e7b8b36 (patch) | |
tree | 23b0400362b5f3035b156200eb634d202aa54741 /src/lib/common | |
parent | 08f33f7d450c8920afc3b9fb8886729547065313 (diff) | |
download | tanzanite-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/common')
-rw-r--r-- | src/lib/common/ButtonPaginator.ts | 186 | ||||
-rw-r--r-- | src/lib/common/DeleteButton.ts | 61 | ||||
-rw-r--r-- | src/lib/common/Format.ts | 72 | ||||
-rw-r--r-- | src/lib/common/autoMod.ts | 52 | ||||
-rw-r--r-- | src/lib/common/moderation.ts | 19 | ||||
-rw-r--r-- | src/lib/common/typings/BushInspectOptions.d.ts | 91 | ||||
-rw-r--r-- | src/lib/common/typings/CodeBlockLang.d.ts | 310 | ||||
-rw-r--r-- | src/lib/common/util/Arg.ts | 120 |
8 files changed, 871 insertions, 40 deletions
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); + } +} |