diff options
-rw-r--r-- | .vscode/settings.json | 6 | ||||
-rw-r--r-- | src/commands/info/pronouns.ts | 62 | ||||
-rw-r--r-- | src/commands/info/userInfo.ts | 5 | ||||
-rw-r--r-- | src/commands/leveling/leaderboard.ts | 2 | ||||
-rw-r--r-- | src/commands/moderation/ban.ts | 51 | ||||
-rw-r--r-- | src/commands/moderation/kick.ts | 5 | ||||
-rw-r--r-- | src/commands/moderation/modlog.ts | 9 | ||||
-rw-r--r-- | src/commands/moderation/mute.ts | 5 | ||||
-rw-r--r-- | src/lib/extensions/discord-akairo/BushClientUtil.ts | 81 | ||||
-rw-r--r-- | src/lib/extensions/discord.js/BushBaseGuildTextChannel.ts | 10 | ||||
-rw-r--r-- | src/lib/extensions/discord.js/BushGuild.ts | 63 | ||||
-rw-r--r-- | src/lib/extensions/discord.js/BushGuildMember.ts | 17 | ||||
-rw-r--r-- | src/lib/extensions/discord.js/BushNewsChannel.ts | 4 | ||||
-rw-r--r-- | src/lib/extensions/discord.js/BushTextChannel.ts | 4 | ||||
-rw-r--r-- | src/lib/models/ModLog.ts | 2 | ||||
-rw-r--r-- | src/lib/utils/BushConstants.ts | 69 | ||||
-rw-r--r-- | yarn.lock | 98 |
17 files changed, 326 insertions, 167 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a28c7c..3e23797 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,14 +6,16 @@ "**/CVS": true, "**/.DS_Store": true, "dist": false, - ".pnp.js": true + ".pnp.js": true, + "**/node_modules": true, }, "javascript.preferences.importModuleSpecifier": "project-relative", "typescript.preferences.importModuleSpecifier": "project-relative", "typescript.preferences.importModuleSpecifierEnding": "minimal", "search.exclude": { "**/.yarn": true, - "**/.pnp.*": true + "**/.pnp.*": true, + "**/node_modules": true, }, "editor.codeActionsOnSave": { "source.organizeImports": true, diff --git a/src/commands/info/pronouns.ts b/src/commands/info/pronouns.ts index ea20d41..77612da 100644 --- a/src/commands/info/pronouns.ts +++ b/src/commands/info/pronouns.ts @@ -1,32 +1,6 @@ import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; import { Snowflake } from 'discord-api-types'; import { MessageEmbed, User } from 'discord.js'; -import got, { HTTPError } from 'got'; - -export const pronounMapping = { - 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' -}; -export type pronounsType = keyof typeof pronounMapping; export default class PronounsCommand extends BushCommand { public constructor() { @@ -62,43 +36,31 @@ export default class PronounsCommand extends BushCommand { }); } override async exec(message: BushMessage | BushSlashMessage, args: { user?: User | Snowflake }): Promise<unknown> { - const user = - args?.user === undefined || args?.user === null - ? message.author - : typeof args.user === 'object' - ? args.user - : await client.users.fetch(`${args.user}`).catch(() => undefined); + const user = (await util.resolveNonCachedUser(args.user)) ?? message.author; - if (user === undefined) return message.util.reply(`${util.emojis.error} Invalid user.`); + if (!user) return message.util.reply(`${util.emojis.error} Invalid user.`); const author = user.id === message.author.id; - try { - const apiRes: { pronouns: pronounsType } = await got - .get(`https://pronoundb.org/api/v1/lookup?platform=discord&id=${user.id}`) - .json(); + + const pronouns = await util.getPronounsOf(user); + if (!pronouns) { + return await message.util.reply( + `${author ? 'You do' : `${user.tag} does`} not appear to have any pronouns set. Please ${ + author ? '' : 'tell them to' + } go to https://pronoundb.org/ and set ${author ? 'your' : 'their'} pronouns.` + ); + } else { return await message.util.reply({ embeds: [ new MessageEmbed({ title: `${author ? 'Your' : `${user.tag}'s`} pronouns:`, - description: pronounMapping[apiRes.pronouns], + description: pronouns, footer: { text: 'Data provided by https://pronoundb.org/' } }) ] }); - } catch (e) { - if (e instanceof HTTPError && e.response.statusCode === 404) { - if (author) { - return await message.util.reply( - 'You do not appear to have any pronouns set. Please go to https://pronoundb.org/ and set your pronouns.' - ); - } else { - return await message.util.reply( - `${user.tag} does not appear to have any pronouns set. Please tell them to go to https://pronoundb.org/ and set their pronouns.` - ); - } - } else throw e; } } } diff --git a/src/commands/info/userInfo.ts b/src/commands/info/userInfo.ts index ae204f7..9fddd67 100644 --- a/src/commands/info/userInfo.ts +++ b/src/commands/info/userInfo.ts @@ -96,6 +96,11 @@ export default class UserInfoCommand extends BushCommand { `**ID:** ${user.id}`, `**Created: **${createdAt} (${createdAtDelta} ago)` ]; + if (user.accentColor !== null) generalInfo.push(`**Accent Color:** ${user.hexAccentColor}`); + if (user.banner) generalInfo.push(`**Banner**: [link](${user.bannerURL({ dynamic: true, format: 'png' })})`); + const pronouns = await util.getPronounsOf(user); + if (pronouns) generalInfo.push(`**Pronouns:** ${pronouns}`); + userEmbed.addField('» General Info', generalInfo.join('\n')); // Server User Info diff --git a/src/commands/leveling/leaderboard.ts b/src/commands/leveling/leaderboard.ts index b8838b7..d29c15e 100644 --- a/src/commands/leveling/leaderboard.ts +++ b/src/commands/leveling/leaderboard.ts @@ -47,6 +47,6 @@ export default class LeaderboardCommand extends BushCommand { const embeds = chunked.map((c) => new MessageEmbed().setTitle(`${message.guild!.name}'s Leaderboard`).setDescription(c.join('\n')) ); - return await util.buttonPaginate(message, embeds, null, true, args?.page ?? undefined); + return await util.buttonPaginate(message, embeds, undefined, true, args?.page ?? undefined); } } diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts index c33b39a..2c3e429 100644 --- a/src/commands/moderation/ban.ts +++ b/src/commands/moderation/ban.ts @@ -1,5 +1,5 @@ -import { AllowedMentions, BushCommand, BushGuildMember, BushMessage, BushSlashMessage } from '@lib'; -import { User } from 'discord.js'; +import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage } from '@lib'; +import { Snowflake, User } from 'discord.js'; export default class BanCommand extends BushCommand { public constructor() { @@ -14,7 +14,7 @@ export default class BanCommand extends BushCommand { args: [ { id: 'user', - type: 'user', + customType: util.arg.union('user', 'snowflake'), prompt: { start: 'What user would you like to ban?', retry: '{error} Choose a valid user to ban.' @@ -83,17 +83,19 @@ export default class BanCommand extends BushCommand { public override async exec( message: BushMessage | BushSlashMessage, { - user, + user: _user, reason, days, force - }: { user: User; reason?: { duration: number; contentWithoutTime: string }; days?: number; force: boolean } + }: { user: User | Snowflake; reason?: { duration: number; contentWithoutTime: string }; days?: number; force: boolean } ): Promise<unknown> { if (!message.guild) return message.util.reply(`${util.emojis.error} This command cannot be used in dms.`); - const member = message.guild!.members.cache.get(user.id) as BushGuildMember; + const member = message.guild!.members.cache.get((_user as User)?.id); + const user = member?.user ?? (await util.resolveNonCachedUser(_user)); + if (!user) return message.util.reply(`${util.emojis.error} Invalid user.`); const useForce = force && message.author.isOwner(); if (!message.member) throw new Error(`message.member is null`); - const canModerateResponse = util.moderationPermissionCheck(message.member, member, 'ban', true, useForce); + const canModerateResponse = member ? util.moderationPermissionCheck(message.member, member, 'ban', true, useForce) : true; if (canModerateResponse !== true) { return message.util.reply(canModerateResponse); @@ -112,31 +114,40 @@ export default class BanCommand extends BushCommand { ? await util.arg.cast('duration', client.commandHandler.resolver, message as BushMessage, reason) : reason.duration; } - const parsedReason = reason?.contentWithoutTime ?? ''; + const parsedReason = reason?.contentWithoutTime ?? null; - const responseCode = await member.bushBan({ - reason: parsedReason, - moderator: message.author, - duration: time! ?? 0, - deleteDays: days ?? 0 - }); + const responseCode = member + ? await member.bushBan({ + reason: parsedReason, + moderator: message.author, + duration: time! ?? 0, + deleteDays: days ?? 0 + }) + : await message.guild.ban({ + user, + reason: parsedReason, + moderator: message.author, + duration: time! ?? 0, + deleteDays: days ?? 0 + }); const responseMessage = () => { switch (responseCode) { case 'missing permissions': - return `${util.emojis.error} Could not ban **${member.user.tag}** because I do not have permissions`; + return `${util.emojis.error} Could not ban **${user.tag}** because I do not have permissions`; case 'error banning': - return `${util.emojis.error} An error occurred while trying to ban **${member.user.tag}**.`; + return `${util.emojis.error} An error occurred while trying to ban **${user.tag}**.`; case 'error creating ban entry': - return `${util.emojis.error} While banning **${member.user.tag}**, there was an error creating a ban entry, please report this to my developers.`; + return `${util.emojis.error} While banning **${user.tag}**, there was an error creating a ban entry, please report this to my developers.`; case 'error creating modlog entry': - return `${util.emojis.error} While banning **${member.user.tag}**, there was an error creating a modlog entry, please report this to my developers.`; + return `${util.emojis.error} While banning **${user.tag}**, there was an error creating a modlog entry, please report this to my developers.`; case 'failed to dm': - return `${util.emojis.warn} Banned **${member.user.tag}** however I could not send them a dm.`; + return `${util.emojis.warn} Banned **${user.tag}** however I could not send them a dm.`; case 'success': - return `${util.emojis.success} Successfully banned **${member.user.tag}**.`; + return `${util.emojis.success} Successfully banned **${user.tag}**.`; } }; + client.console.debug(responseCode); return await message.util.reply({ content: responseMessage(), allowedMentions: AllowedMentions.none() }); } } diff --git a/src/commands/moderation/kick.ts b/src/commands/moderation/kick.ts index 2315712..341d83c 100644 --- a/src/commands/moderation/kick.ts +++ b/src/commands/moderation/kick.ts @@ -61,7 +61,10 @@ export default class KickCommand extends BushCommand { ): Promise<unknown> { const member = message.guild!.members.cache.get(user.id) as BushGuildMember; - if (!member) return await message.util.reply(`${util.emojis.error} You cannot kick members that are not in the server.`); + if (!member) + return await message.util.reply( + `${util.emojis.error} The user you selected is not in the server or is not a valid user.` + ); if (!message.member) throw new Error(`message.member is null`); const useForce = force && message.author.isOwner(); const canModerateResponse = util.moderationPermissionCheck(message.member, member, 'kick', true, useForce); diff --git a/src/commands/moderation/modlog.ts b/src/commands/moderation/modlog.ts index ef0a56e..fd53ea7 100644 --- a/src/commands/moderation/modlog.ts +++ b/src/commands/moderation/modlog.ts @@ -35,6 +35,7 @@ export default class ModlogCommand extends BushCommand { } #generateModlogInfo(log: ModLog): string { + const trim = (str: string): string => (str.endsWith('\n') ? str.substring(0, str.length - 1).trim() : str.trim()); const modLog = [ `**Case ID**: ${log.id}`, `**Type**: ${log.type.toLowerCase()}`, @@ -42,8 +43,8 @@ export default class ModlogCommand extends BushCommand { `**Moderator**: <@!${log.moderator}> (${log.moderator})` ]; if (log.duration) modLog.push(`**Duration**: ${util.humanizeDuration(log.duration)}`); - modLog.push(`**Reason**: ${log.reason ?? 'No Reason Specified.'}`); - if (log.evidence) modLog.push(`**Evidence:** ${log.evidence}`); + modLog.push(`**Reason**: ${trim(log.reason ?? 'No Reason Specified.')}`); + if (log.evidence) modLog.push(`**Evidence:** ${trim(log.evidence)}`); return modLog.join(`\n`); } @@ -70,11 +71,11 @@ export default class ModlogCommand extends BushCommand { (chunk) => new MessageEmbed({ title: `${foundUser.tag}'s Mod Logs`, - description: chunk.join('\n**―――――――――――――――――――――――――――**\n'), + description: chunk.join('\n━━━━━━━━━━━━━━━\n'), color: util.colors.default }) ); - return await util.buttonPaginate(message, embedPages, '', true); + return await util.buttonPaginate(message, embedPages, undefined, true); } else if (search) { const entry = await ModLog.findByPk(search as string); if (!entry) return message.util.send(`${util.emojis.error} That modlog does not exist.`); diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts index 915302e..ea2ff41 100644 --- a/src/commands/moderation/mute.ts +++ b/src/commands/moderation/mute.ts @@ -61,7 +61,10 @@ export default class MuteCommand extends BushCommand { { user, reason, force }: { user: BushUser; reason?: { duration: number; contentWithoutTime: string }; force: boolean } ): Promise<unknown> { const member = message.guild!.members.cache.get(user.id); - if (!member) return await message.util.reply(`${util.emojis.error} You cannot kick members that are not in the server.`); + if (!member) + return await message.util.reply( + `${util.emojis.error} The user you selected is not in the server or is not a valid user.` + ); if (!message.member) throw new Error(`message.member is null`); const useForce = force && message.author.isOwner(); diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index a7dd535..55f525b 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -13,7 +13,9 @@ import { Global, Guild, ModLog, - ModLogType + ModLogType, + Pronoun, + PronounCode } from '@lib'; import { exec } from 'child_process'; import { @@ -27,7 +29,6 @@ import { } from 'discord-akairo'; import { APIMessage } from 'discord-api-types'; import { - ButtonInteraction, ColorResolvable, CommandInteraction, Constants, @@ -41,13 +42,16 @@ import { MessageOptions, Snowflake, TextChannel, + ThreadMember, User, + UserResolvable, Util as DiscordUtil } from 'discord.js'; import got from 'got'; import humanizeDuration from 'humanize-duration'; import _ from 'lodash'; import moment from 'moment'; +import fetch from 'node-fetch'; import { inspect, InspectOptions, promisify } from 'util'; import CommandErrorListener from '../../../listeners/commands/commandError'; import { ActivePunishment, ActivePunishmentType } from '../../models/ActivePunishment'; @@ -690,7 +694,7 @@ export class BushClientUtil extends ClientUtil { }); const style = Constants.MessageButtonStyles.PRIMARY; - let curPage = startOn ? startOn - 1 : undefined ?? 0; + 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 @@ -698,11 +702,11 @@ export class BushClientUtil extends ClientUtil { embeds: [embeds[curPage]], components: [getPaginationRow()] })) as Message; - const filter = (interaction: ButtonInteraction) => - interaction.customId.startsWith('paginate_') && interaction.message == msg; + 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)) { + if (interaction.user.id === message.author.id || client.config.owners.includes(interaction.user.id)) { switch (interaction.customId) { case 'paginate_beginning': { curPage = 0; @@ -722,7 +726,11 @@ export class BushClientUtil extends ClientUtil { } } else { await interaction - ?.update({ content: `${text ? text + '\n' : ''}Command closed by user.`, embeds: [], components: [] }) + ?.update({ + content: `${text ? text + '\n' : ''}Command closed by user.`, + embeds: [], + components: [] + }) .catch(() => undefined); } return; @@ -744,7 +752,13 @@ export class BushClientUtil extends ClientUtil { }); collector.on('end', async () => { - await msg.edit({ content: text, embeds: [embeds[curPage]], components: [getPaginationRow(true)] }).catch(() => undefined); + await msg + .edit({ + content: text, + embeds: [embeds[curPage]], + components: [getPaginationRow(true)] + }) + .catch(() => undefined); }); async function edit(interaction: MessageComponentInteraction): Promise<void> { @@ -766,7 +780,12 @@ export class BushClientUtil extends ClientUtil { emoji: paginateEmojis.back, disabled: disableAll || curPage == 0 }), - new MessageButton({ style, customId: 'paginate_stop', emoji: paginateEmojis.stop, disabled: disableAll }), + new MessageButton({ + style, + customId: 'paginate_stop', + emoji: paginateEmojis.stop, + disabled: disableAll + }), new MessageButton({ style, customId: 'paginate_next', @@ -790,7 +809,8 @@ export class BushClientUtil extends ClientUtil { const paginateEmojis = this.#paginateEmojis; updateOptions(); const msg = (await message.util.reply(options as MessageOptions & { split?: false })) as Message; - const filter = (interaction: ButtonInteraction) => interaction.customId == 'paginate__stop' && interaction.message == msg; + 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)) { @@ -1082,14 +1102,14 @@ export class BushClientUtil extends ClientUtil { type: ModLogType; user: BushGuildMemberResolvable; moderator: BushGuildMemberResolvable; - reason: string | undefined; + reason: string | undefined | null; duration?: number; guild: BushGuildResolvable; }, getCaseNumber = false ): Promise<{ log: ModLog | null; caseNum: number | null }> { - const user = client.users.resolveId(options.user)!; - const moderator = client.users.resolveId(options.moderator)!; + const user = (await util.resolveNonCachedUser(options.user))!.id; + const moderator = (await util.resolveNonCachedUser(options.moderator))!.id; const guild = client.guilds.resolveId(options.guild)!; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const duration = options.duration || undefined; @@ -1131,10 +1151,10 @@ export class BushClientUtil extends ClientUtil { modlog?: string; extraInfo?: Snowflake; }): Promise<ActivePunishment | null> { - const expires = options.duration ? new Date(new Date().getTime() + options.duration) : undefined; - client.console.debug(expires); + const expires = options.duration ? new Date(new Date().getTime() + options.duration ?? 0) : undefined; + client.console.debug(expires, 1); client.console.debug(typeof expires); - const user = client.users.resolveId(options.user)!; + const user = (await util.resolveNonCachedUser(options.user))!.id; const guild = client.guilds.resolveId(options.guild)!; const type = this.#findTypeEnum(options.type)!; @@ -1152,7 +1172,7 @@ export class BushClientUtil extends ClientUtil { user: BushGuildMemberResolvable; guild: BushGuildResolvable; }): Promise<boolean> { - const user = client.users.resolveId(options.user); + const user = await util.resolveNonCachedUser(options.user); const guild = client.guilds.resolveId(options.guild); const type = this.#findTypeEnum(options.type); @@ -1366,6 +1386,33 @@ export class BushClientUtil extends ClientUtil { }); } + public async resolveNonCachedUser(user: UserResolvable | undefined | null): Promise<User | undefined> { + if (!user) return undefined; + const id = + user instanceof User || user instanceof GuildMember || user instanceof ThreadMember + ? user.id + : user instanceof Message + ? user.author.id + : typeof user === 'string' + ? user + : undefined; + if (!id) return undefined; + else return await client.users.fetch(id).catch(() => undefined); + } + + 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: { pronouns: PronounCode } | undefined = await fetch( + `https://pronoundb.org/api/v1/lookup?platform=discord&id=${_user.id}` + ).then(async (r) => (r.ok ? ((await r.json()) as { pronouns: PronounCode }) : undefined)); + + if (!apiRes) return undefined; + if (!apiRes.pronouns) throw new Error('apiRes.pronouns is undefined'); + + return client.constants.pronounMapping[apiRes.pronouns]; + } + //~ modified from https://stackoverflow.com/questions/31054910/get-functions-methods-of-a-class //~ answer by Bruno Grieder //~ public getMethods(obj: any): string { diff --git a/src/lib/extensions/discord.js/BushBaseGuildTextChannel.ts b/src/lib/extensions/discord.js/BushBaseGuildTextChannel.ts index 734642e..78cfada 100644 --- a/src/lib/extensions/discord.js/BushBaseGuildTextChannel.ts +++ b/src/lib/extensions/discord.js/BushBaseGuildTextChannel.ts @@ -1,4 +1,10 @@ -import { AllowedThreadTypeForTextChannel, BaseGuildTextChannel, Collection, Snowflake } from 'discord.js'; +import { + AllowedThreadTypeForNewsChannel, + AllowedThreadTypeForTextChannel, + BaseGuildTextChannel, + Collection, + Snowflake +} from 'discord.js'; import { RawGuildChannelData } from 'discord.js/typings/rawDataTypes'; import { BushCategoryChannel, BushClient, BushGuildMember } from '../..'; import { BushGuild } from './BushGuild'; @@ -10,7 +16,7 @@ export class BushBaseGuildTextChannel extends BaseGuildTextChannel { super(guild, data, client, immediatePatch); } public declare messages: BushMessageManager; - public declare threads: BushThreadManager<AllowedThreadTypeForTextChannel>; + public declare threads: BushThreadManager<AllowedThreadTypeForTextChannel | AllowedThreadTypeForNewsChannel>; public declare readonly client: BushClient; public declare guild: BushGuild; public declare readonly members: Collection<Snowflake, BushGuildMember>; diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts index 09e355c..12db49a 100644 --- a/src/lib/extensions/discord.js/BushGuild.ts +++ b/src/lib/extensions/discord.js/BushGuild.ts @@ -1,4 +1,4 @@ -import { Guild } from 'discord.js'; +import { Guild, UserResolvable } from 'discord.js'; import { RawGuildData } from 'discord.js/typings/rawDataTypes'; import { Guild as GuildDB, GuildFeatures, GuildModel } from '../../models/Guild'; import { ModLogType } from '../../models/ModLog'; @@ -52,9 +52,56 @@ export class BushGuild extends Guild { return await row.save(); } + public async ban(options: { + user: BushUserResolvable | UserResolvable; + reason?: string | null; + moderator?: BushUserResolvable; + duration?: number; + deleteDays?: number; + }): Promise< + 'success' | 'missing permissions' | 'error banning' | 'error creating modlog entry' | 'error creating ban entry' + > { + // checks + if (!this.me!.permissions.has('BAN_MEMBERS')) return 'missing permissions'; + + const moderator = (await util.resolveNonCachedUser(options.moderator!)) ?? client.user!; + + // ban + const banSuccess = await this.bans + .create(options.user, { + reason: `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`, + days: options.deleteDays + }) + .catch(() => false); + if (!banSuccess) return 'error banning'; + + // add modlog entry + const { log: modlog } = await util.createModLogEntry({ + type: options.duration ? ModLogType.TEMP_BAN : ModLogType.PERM_BAN, + user: options.user as BushUserResolvable, + moderator: moderator.id, + reason: options.reason, + duration: options.duration, + guild: this + }); + if (!modlog) return 'error creating modlog entry'; + + // add punishment entry so they can be unbanned later + const punishmentEntrySuccess = await util.createPunishmentEntry({ + type: 'ban', + user: options.user as BushUserResolvable, + guild: this, + duration: options.duration, + modlog: modlog.id + }); + if (!punishmentEntrySuccess) return 'error creating ban entry'; + + return 'success'; + } + public async unban(options: { user: BushUserResolvable | BushUser; - reason?: string; + reason?: string | null; moderator?: BushUserResolvable; }): Promise< | 'success' @@ -64,13 +111,13 @@ export class BushGuild extends Guild { | 'error creating modlog entry' | 'error removing ban entry' > { - const user = client.users.resolveId(options.user)!; - const moderator = client.users.cache.get(client.users.resolveId(options.moderator!)!)!; + const user = (await util.resolveNonCachedUser(options.user))!; + const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.me))!; const bans = await this.bans.fetch(); let notBanned = false; - if (!bans.has(user)) notBanned = true; + if (!bans.has(user.id)) notBanned = true; const unbanSuccess = await this.bans .remove(user, `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`) @@ -86,7 +133,7 @@ export class BushGuild extends Guild { // add modlog entry const modlog = await util.createModLogEntry({ type: ModLogType.UNBAN, - user, + user: user.id, moderator: moderator.id, reason: options.reason, guild: this @@ -96,12 +143,12 @@ export class BushGuild extends Guild { // remove punishment entry const removePunishmentEntrySuccess = await util.removePunishmentEntry({ type: 'ban', - user, + user: user.id, guild: this }); if (!removePunishmentEntrySuccess) return 'error removing ban entry'; - const userObject = client.users.cache.get(user); + const userObject = client.users.cache.get(user.id); userObject?.send(`You have been unbanned from **${this}** for **${options.reason ?? 'No reason provided'}**.`); diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts index f71a435..67fa2fa 100644 --- a/src/lib/extensions/discord.js/BushGuildMember.ts +++ b/src/lib/extensions/discord.js/BushGuildMember.ts @@ -8,7 +8,7 @@ import { BushRole } from './BushRole'; import { BushUser } from './BushUser'; interface BushPunishmentOptions { - reason?: string; + reason?: string | null; moderator?: BushUserResolvable; } @@ -87,7 +87,8 @@ export class BushGuildMember extends GuildMember { } public async warn(options: BushPunishmentOptions): Promise<{ result: WarnResponse | null; caseNum: number | null }> { - const moderator = client.users.cache.get(client.users.resolveId(options.moderator!)!) ?? client.user!; + const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; + // add modlog entry const result = await util.createModLogEntry( { @@ -119,7 +120,7 @@ export class BushGuildMember extends GuildMember { const ifShouldAddRole = this.#checkIfShouldAddRole(options.role); if (ifShouldAddRole !== true) return ifShouldAddRole; - const moderator = client.users.cache.get(client.users.resolveId(options.moderator!)!) ?? client.user!; + const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; if (options.addToModlog || options.duration) { const { log: modlog } = options.addToModlog @@ -159,7 +160,7 @@ export class BushGuildMember extends GuildMember { const ifShouldAddRole = this.#checkIfShouldAddRole(options.role); if (ifShouldAddRole !== true) return ifShouldAddRole; - const moderator = client.users.cache.get(client.users.resolveId(options.moderator!)!) ?? client.user!; + const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; if (options.addToModlog) { const { log: modlog } = await util.createModLogEntry({ @@ -207,7 +208,7 @@ export class BushGuildMember extends GuildMember { if (!muteRole) return 'invalid mute role'; if (muteRole.position >= this.guild.me!.roles.highest.position || muteRole.managed) return 'mute role not manageable'; - const moderator = client.users.cache.get(client.users.resolveId(options.moderator!)!) ?? client.user!; + const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; // add role const muteSuccess = await this.roles @@ -264,7 +265,7 @@ export class BushGuildMember extends GuildMember { if (!muteRole) return 'invalid mute role'; if (muteRole.position >= this.guild.me!.roles.highest.position || muteRole.managed) return 'mute role not manageable'; - const moderator = client.users.cache.get(client.users.resolveId(options.moderator!)!) ?? client.user!; + const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; //remove role const muteSuccess = await this.roles @@ -309,7 +310,7 @@ export class BushGuildMember extends GuildMember { // checks if (!this.guild.me?.permissions.has('KICK_MEMBERS') || !this.kickable) return 'missing permissions'; - const moderator = client.users.cache.get(client.users.resolveId(options.moderator!)!) ?? client.user!; + const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; // dm user const ending = await this.guild.getSetting('punishmentEnding'); @@ -340,7 +341,7 @@ export class BushGuildMember extends GuildMember { // checks if (!this.guild.me!.permissions.has('BAN_MEMBERS') || !this.bannable) return 'missing permissions'; - const moderator = client.users.cache.get(client.users.resolveId(options.moderator!)!) ?? client.user!; + const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; // dm user const ending = await this.guild.getSetting('punishmentEnding'); diff --git a/src/lib/extensions/discord.js/BushNewsChannel.ts b/src/lib/extensions/discord.js/BushNewsChannel.ts index f44ff4a..d770132 100644 --- a/src/lib/extensions/discord.js/BushNewsChannel.ts +++ b/src/lib/extensions/discord.js/BushNewsChannel.ts @@ -1,4 +1,4 @@ -import { AllowedThreadTypeForTextChannel, Collection, NewsChannel, Snowflake } from 'discord.js'; +import { AllowedThreadTypeForNewsChannel, Collection, NewsChannel, Snowflake } from 'discord.js'; import { BushClient } from '../discord-akairo/BushClient'; import { BushGuild } from './BushGuild'; import { BushGuildMember } from './BushGuildMember'; @@ -7,8 +7,8 @@ import { BushThreadManager } from './BushThreadManager'; export class BushNewsChannel extends NewsChannel { public declare readonly client: BushClient; + public declare threads: BushThreadManager<AllowedThreadTypeForNewsChannel>; public declare guild: BushGuild; public declare messages: BushMessageManager; public declare members: Collection<Snowflake, BushGuildMember>; - public declare threads: BushThreadManager<AllowedThreadTypeForTextChannel>; } diff --git a/src/lib/extensions/discord.js/BushTextChannel.ts b/src/lib/extensions/discord.js/BushTextChannel.ts index db3ad8f..1d4d7fe 100644 --- a/src/lib/extensions/discord.js/BushTextChannel.ts +++ b/src/lib/extensions/discord.js/BushTextChannel.ts @@ -1,13 +1,15 @@ -import { TextChannel } from 'discord.js'; +import { AllowedThreadTypeForTextChannel, TextChannel } from 'discord.js'; import { RawGuildChannelData } from 'discord.js/typings/rawDataTypes'; import { BushClient } from '../discord-akairo/BushClient'; import { BushGuild } from './BushGuild'; import { BushMessageManager } from './BushMessageManager'; +import { BushThreadManager } from './BushThreadManager'; export class BushTextChannel extends TextChannel { public declare readonly client: BushClient; public declare guild: BushGuild; public declare messages: BushMessageManager; + public declare threads: BushThreadManager<AllowedThreadTypeForTextChannel>; public constructor(guild: BushGuild, data?: RawGuildChannelData) { super(guild, data); } diff --git a/src/lib/models/ModLog.ts b/src/lib/models/ModLog.ts index 7787375..0be1ea7 100644 --- a/src/lib/models/ModLog.ts +++ b/src/lib/models/ModLog.ts @@ -37,7 +37,7 @@ export interface ModLogModelCreationAttributes { type: ModLogType; user: Snowflake; moderator: Snowflake; - reason?: string; + reason?: string | null; duration?: number; guild: Snowflake; evidence?: string; diff --git a/src/lib/utils/BushConstants.ts b/src/lib/utils/BushConstants.ts index f2ca327..17880cb 100644 --- a/src/lib/utils/BushConstants.ts +++ b/src/lib/utils/BushConstants.ts @@ -22,6 +22,51 @@ interface bushColors { orange: '#E86100'; } +export type PronounCode = + | 'unspecified' + | 'hh' + | 'hi' + | 'hs' + | 'ht' + | 'ih' + | 'ii' + | 'is' + | 'it' + | 'shh' + | 'sh' + | 'si' + | 'st' + | 'th' + | 'ti' + | 'ts' + | 'tt' + | 'any' + | 'other' + | 'ask' + | 'avoid'; +export type Pronoun = + | 'Unspecified' + | 'He/Him' + | 'He/It' + | 'He/She' + | 'He/They' + | 'It/Him' + | 'It/Its' + | 'It/She' + | 'It/They' + | 'She/He' + | 'She/Her' + | 'She/It' + | 'She/They' + | 'They/He' + | 'They/It' + | 'They/She' + | 'They/Them' + | 'Any pronouns' + | 'Other pronouns' + | 'Ask me my pronouns' + | 'Avoid pronouns, use my name'; + export class BushConstants { public static emojis = { success: '<:success:837109864101707807>', @@ -108,6 +153,30 @@ export class BushConstants { discordEmoji: /<a?:(?<name>[a-zA-Z0-9\_]+):(?<id>\d{15,21})>/im }; + public static pronounMapping: { [x in PronounCode]: Pronoun } = { + 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' + }; + /** A bunch of mappings */ public static mappings = { guilds: { @@ -359,9 +359,9 @@ __metadata: linkType: hard "@types/node@npm:*": - version: 16.7.6 - resolution: "@types/node@npm:16.7.6" - checksum: a8533386a1d4ca0ed67885413001af8789c63948df288f3d36e31bd8fccffacf5dffb95e190c8cd57bb40385f010fb9a30f596bad6bb26b2bb88737d54d8ed95 + version: 16.7.8 + resolution: "@types/node@npm:16.7.8" + checksum: 060ea222ce8f3eb05bd86a7785ca5807503a50602dd805c5be997a5ae684fa6224c9ad8890bcc5551c05d14a8bcd735f94567691342d197f5f7f7f893ed0d46b languageName: node linkType: hard @@ -436,11 +436,11 @@ __metadata: linkType: hard "@typescript-eslint/eslint-plugin@npm:^4.14.1": - version: 4.29.3 - resolution: "@typescript-eslint/eslint-plugin@npm:4.29.3" + version: 4.30.0 + resolution: "@typescript-eslint/eslint-plugin@npm:4.30.0" dependencies: - "@typescript-eslint/experimental-utils": 4.29.3 - "@typescript-eslint/scope-manager": 4.29.3 + "@typescript-eslint/experimental-utils": 4.30.0 + "@typescript-eslint/scope-manager": 4.30.0 debug: ^4.3.1 functional-red-black-tree: ^1.0.1 regexpp: ^3.1.0 @@ -452,66 +452,66 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: ded1580fc6348848df3ed92d4365586bf13a05cd698c07aa7727155ca13788b5c33bd326b5435af3e97b702156b1eef811ace20fb5ca44eab6388cecfd8e264a + checksum: 131079d73537d960e8bfbf56df9687e0eaa9b0b6791a029d2f4fd367ebe8db56086361e5ebff74be95bef40855f1dd3f9145bd1b02d48e547a9896256e4648f7 languageName: node linkType: hard -"@typescript-eslint/experimental-utils@npm:4.29.3": - version: 4.29.3 - resolution: "@typescript-eslint/experimental-utils@npm:4.29.3" +"@typescript-eslint/experimental-utils@npm:4.30.0": + version: 4.30.0 + resolution: "@typescript-eslint/experimental-utils@npm:4.30.0" dependencies: "@types/json-schema": ^7.0.7 - "@typescript-eslint/scope-manager": 4.29.3 - "@typescript-eslint/types": 4.29.3 - "@typescript-eslint/typescript-estree": 4.29.3 + "@typescript-eslint/scope-manager": 4.30.0 + "@typescript-eslint/types": 4.30.0 + "@typescript-eslint/typescript-estree": 4.30.0 eslint-scope: ^5.1.1 eslint-utils: ^3.0.0 peerDependencies: eslint: "*" - checksum: 7cd398bf3fccee1c769006c9d28fc0a353c2978cbc33e21449d186ab413ccf5f731b3ac30f557550c1daac767a5b97dce15ec10fe9ad5a632846d285dafac5b0 + checksum: 2f63ec6a463edd4e41669febb16160590044a4a065b3d9badebcbe97b1725d1264390135cfe5cc3deb567df7b086f810763b761eddca5d901b9bdf1a462b03b3 languageName: node linkType: hard "@typescript-eslint/parser@npm:^4.14.1": - version: 4.29.3 - resolution: "@typescript-eslint/parser@npm:4.29.3" + version: 4.30.0 + resolution: "@typescript-eslint/parser@npm:4.30.0" dependencies: - "@typescript-eslint/scope-manager": 4.29.3 - "@typescript-eslint/types": 4.29.3 - "@typescript-eslint/typescript-estree": 4.29.3 + "@typescript-eslint/scope-manager": 4.30.0 + "@typescript-eslint/types": 4.30.0 + "@typescript-eslint/typescript-estree": 4.30.0 debug: ^4.3.1 peerDependencies: eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 3fac6b5219de8b9aea361cc2fa170105661068d5eee5594f2f68526801a66b9525a766fc17427a8d410ada0da2d852f8c021d0b2fac7442a1e913f248ac85d90 + checksum: aed2048c5eade38a57db50f551245b72cd460c3f8c38f51f3b62ce7715d43b691a91bf277b0a4f5b5e0383fb5d8a93ff0501187e2454eb6d128da34ef802e3ab languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:4.29.3": - version: 4.29.3 - resolution: "@typescript-eslint/scope-manager@npm:4.29.3" +"@typescript-eslint/scope-manager@npm:4.30.0": + version: 4.30.0 + resolution: "@typescript-eslint/scope-manager@npm:4.30.0" dependencies: - "@typescript-eslint/types": 4.29.3 - "@typescript-eslint/visitor-keys": 4.29.3 - checksum: 53a4d3cd0844df789ad3548644d9214cf234ce87bbc7843c55949f63e98925b4685b36f0514afbab891b4f8f0da85c249850023be5d5e9b175780aa62d181aac + "@typescript-eslint/types": 4.30.0 + "@typescript-eslint/visitor-keys": 4.30.0 + checksum: 7756f13cb5fc103e61c765ad8d15249804cec8c13c2051a63c0d543307ab178052f5bcb4a19cb50df281b4097b728f33ba92c83b670a091a51a69795ac8e3b86 languageName: node linkType: hard -"@typescript-eslint/types@npm:4.29.3": - version: 4.29.3 - resolution: "@typescript-eslint/types@npm:4.29.3" - checksum: 26fd2bd6782b763ff6d5ef3bcc08e1d29b64d15ef6f3604203f6171517935d822c103f803d8755c8e0cb77319143e5d5108dc90e8e897c8e72bab9f178be67ce +"@typescript-eslint/types@npm:4.30.0": + version: 4.30.0 + resolution: "@typescript-eslint/types@npm:4.30.0" + checksum: 1be5c9a30c1e552b5b215fe1ca38f3ac48ea68a06c103eb498d3a987b8cbdbe4770992d4498ab28e216fa9b2d2806ee3c856cc97cbca1a7f01e8032537bd6bef languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:4.29.3": - version: 4.29.3 - resolution: "@typescript-eslint/typescript-estree@npm:4.29.3" +"@typescript-eslint/typescript-estree@npm:4.30.0": + version: 4.30.0 + resolution: "@typescript-eslint/typescript-estree@npm:4.30.0" dependencies: - "@typescript-eslint/types": 4.29.3 - "@typescript-eslint/visitor-keys": 4.29.3 + "@typescript-eslint/types": 4.30.0 + "@typescript-eslint/visitor-keys": 4.30.0 debug: ^4.3.1 globby: ^11.0.3 is-glob: ^4.0.1 @@ -520,17 +520,17 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: b7ea37db1a2f43806bf16090dfb44c7243ad07b7cb75d398fc2a1ce347fa04a59a5c729a41d1e34862cc3ed60275f5565fe3343393df1c42d95395ed42c761f0 + checksum: e35410e33b5afc010f8bde683f4fd4f9fbe3ad821e916eb5ff1ed23943e6202a817a4cb21eb8e697a55f01182ceecaca1731cb98791d767f7fe67b11f84f3d67 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:4.29.3": - version: 4.29.3 - resolution: "@typescript-eslint/visitor-keys@npm:4.29.3" +"@typescript-eslint/visitor-keys@npm:4.30.0": + version: 4.30.0 + resolution: "@typescript-eslint/visitor-keys@npm:4.30.0" dependencies: - "@typescript-eslint/types": 4.29.3 + "@typescript-eslint/types": 4.30.0 eslint-visitor-keys: ^2.0.0 - checksum: 76d485cb573cfccb8a6aded5b98fd58266c10f82362685d3d0b870e197cbe5e3d61b485e220a7a973765c4861df9ea52a35757ecb818f125e405925556ee1f90 + checksum: b825d1340c3209390c39b31f67c995b5a25db716088822c5d056889b700fffb4cd9acb89d75a26db034e52fdbc67d0f7fbfc03ce502f52bcd300ce2379a9aaac languageName: node linkType: hard @@ -1117,12 +1117,12 @@ discord-akairo-message-util@NotEnoughUpdates/discord-akairo-message-util: discord-akairo@NotEnoughUpdates/discord-akairo: version: 8.2.2 - resolution: "discord-akairo@https://github.com/NotEnoughUpdates/discord-akairo.git#commit=8c34349a3eb03164e34bcf538787cce259c76aa9" + resolution: "discord-akairo@https://github.com/NotEnoughUpdates/discord-akairo.git#commit=8da50867c05265aa01fbc08fd510af4c81f64651" dependencies: discord-akairo-message-util: NotEnoughUpdates/discord-akairo-message-util lodash: ^4.17.21 source-map-support: ^0.5.19 - checksum: 7b71844b4955fed0f383b59e23770e526140459dc55b3a8af4707bd6be7a9295c2ac6812df47282a5b922a50f4569b597151f21f893ecce7ef9b1e8b87f88a64 + checksum: c96a613e59726a25a65c8a15e24850447d1c43da7ab52e4a88c1eee55b3a981a98a41f2c41e6e3fad7a3865566f4345f92211d6d4b4198059e88fe9e2c247203 languageName: node linkType: hard @@ -1142,7 +1142,7 @@ discord-akairo@NotEnoughUpdates/discord-akairo: discord.js@NotEnoughUpdates/discord.js: version: 13.2.0-dev - resolution: "discord.js@https://github.com/NotEnoughUpdates/discord.js.git#commit=a1764c7ece9e6dc83484a1bf2f0ac9ca5a501d0a" + resolution: "discord.js@https://github.com/NotEnoughUpdates/discord.js.git#commit=adfd990b6de491c01adb414b231a7a5f3c62042e" dependencies: "@discordjs/builders": ^0.5.0 "@discordjs/collection": ^0.2.1 @@ -1152,7 +1152,7 @@ discord.js@NotEnoughUpdates/discord.js: discord-api-types: ^0.22.0 node-fetch: ^2.6.1 ws: ^7.5.1 - checksum: 54d4a52cba0358317b9c1d2d1d55cdd66987a2489db3a7443dae5220f3a7f532fddf9cd5c8c1d6e8e2f6d1cdcd9b95cf6dc481208724f11d437ddd6c51dc4f4b + checksum: 5bb831df1083ed06ce59fd1500158981f6697d15194b46840d133e1ee32659291c497bb7bddcdabe1f0ce9cf5129febd07f2956039f842e21efa7a8c7bba25e4 languageName: node linkType: hard @@ -2702,9 +2702,9 @@ discord.js@NotEnoughUpdates/discord.js: linkType: hard "resolve-alpn@npm:^1.0.0": - version: 1.2.0 - resolution: "resolve-alpn@npm:1.2.0" - checksum: a38b5bf2084d384586fe15b31735396cff4640cbe137c87ffd1dcc94dcdc0c743e6875ec4baa7a9eed460355a46e3f9ebafa4cdbb8122264ed5f4e3260f69d0c + version: 1.2.1 + resolution: "resolve-alpn@npm:1.2.1" + checksum: f558071fcb2c60b04054c99aebd572a2af97ef64128d59bef7ab73bd50d896a222a056de40ffc545b633d99b304c259ea9d0c06830d5c867c34f0bfa60b8eae0 languageName: node linkType: hard |