diff options
-rw-r--r-- | src/commands/utilities/highlight-!.ts | 245 | ||||
-rw-r--r-- | src/commands/utilities/highlight-add.ts | 30 | ||||
-rw-r--r-- | src/commands/utilities/highlight-block.ts | 70 | ||||
-rw-r--r-- | src/commands/utilities/highlight-clear.ts | 2 | ||||
-rw-r--r-- | src/commands/utilities/highlight-matches.ts | 10 | ||||
-rw-r--r-- | src/commands/utilities/highlight-remove.ts | 12 | ||||
-rw-r--r-- | src/commands/utilities/highlight-show.ts | 11 | ||||
-rw-r--r-- | src/commands/utilities/highlight-unblock.ts | 66 | ||||
-rw-r--r-- | src/lib/common/HighlightManager.ts | 105 |
9 files changed, 336 insertions, 215 deletions
diff --git a/src/commands/utilities/highlight-!.ts b/src/commands/utilities/highlight-!.ts index 6847737..a4b8f42 100644 --- a/src/commands/utilities/highlight-!.ts +++ b/src/commands/utilities/highlight-!.ts @@ -1,84 +1,142 @@ import { BushCommand, clientSendAndPermCheck, Highlight, HighlightWord, type SlashMessage } from '#lib'; import { Flag, type ArgumentGeneratorReturn, type SlashOption } from 'discord-akairo'; -import { - ApplicationCommandOptionType, - ApplicationCommandSubCommandData, - type AutocompleteInteraction, - type CacheType -} from 'discord.js'; +import { ApplicationCommandOptionType, Constants, type AutocompleteInteraction, type CacheType } from 'discord.js'; +import { DeepWritable } from 'ts-essentials'; -type Unpacked<T> = T extends (infer U)[] ? U : T; - -export const highlightCommandArgs: { - [Command in keyof typeof highlightSubcommands]: (Unpacked<Required<ApplicationCommandSubCommandData['options']>> & { - retry?: string; - })[]; -} = { - add: [ - { - name: 'word', - description: 'What word do you want to highlight?', - retry: '{error} Enter a valid word.', - type: ApplicationCommandOptionType.String, - required: true - } - // { - // name: 'regex', - // description: 'Should the word be matched using regular expression?', - // type: ApplicationCommandOptionType.Boolean, - // required: false - // } - ], - remove: [ - { - name: 'word', - description: 'Which word do you want to stop highlighting?', - retry: '{error} Enter a valid word.', - type: ApplicationCommandOptionType.String, - required: true, - autocomplete: true - } - ], - block: [ - { - name: 'target', - description: 'What user/channel would you like to prevent from triggering your highlights?', - retry: '{error} Enter a valid user or channel.', - type: ApplicationCommandOptionType.String, - required: true - } - ], - unblock: [ - { - name: 'target', - description: 'What user/channel would you like to allow triggering your highlights again?', - retry: '{error} Enter a valid user or channel.', - type: ApplicationCommandOptionType.String, - required: true - } - ], - show: [], - clear: [], - matches: [ - { - name: 'phrase', - description: 'What phrase would you like to test your highlighted words against?', - retry: '{error} Enter a valid phrase to test.', - type: ApplicationCommandOptionType.String, - required: true - } - ] -}; +function deepWriteable<T>(obj: T): DeepWritable<T> { + return obj as DeepWritable<T>; +} -export const highlightSubcommands = { - add: 'Add a word to highlight.', - remove: 'Stop highting a word.', - block: 'Block a user or channel from triggering your highlights.', - unblock: 'Re-allow a user or channel to triggering your highlights.', - show: 'List all your current highlighted words.', - clear: 'Remove all of your highlighted words.', - matches: 'Test a phrase to see if it matches your current highlighted words.' -} as const; +export const highlightSubcommands = deepWriteable({ + add: { + description: 'Add a word to highlight.', + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: 'word', + description: 'What word do you want to highlight?', + retry: '{error} Enter a valid word.', + type: ApplicationCommandOptionType.String, + required: true + } + /* { + name: 'regex', + description: 'Should the word be matched using regular expression?', + type: ApplicationCommandOptionType.Boolean, + required: false + } */ + ] + }, + remove: { + description: 'Stop highting a word.', + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: 'word', + description: 'Which word do you want to stop highlighting?', + retry: '{error} Enter a valid word.', + type: ApplicationCommandOptionType.String, + required: true, + autocomplete: true + } + ] + }, + block: { + description: 'Block a user or channel from triggering your highlights.', + type: ApplicationCommandOptionType.SubcommandGroup, + options: [ + { + name: 'user', + description: 'Block a user from triggering your highlights.', + start: 'What user/channel would you like to prevent from triggering your highlights?', + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: 'target', + description: 'What user would you like to prevent from triggering your highlights?', + retry: '{error} Enter a valid user or channel.', + type: ApplicationCommandOptionType.User, + resolve: 'Member', + required: true + } + ] + }, + { + name: 'channel', + description: 'Block a channel from triggering your highlights.', + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: 'target', + description: 'What channel would you like to prevent from triggering your highlights?', + type: ApplicationCommandOptionType.Channel, + channelTypes: Constants.TextBasedChannelTypes, + required: true + } + ] + } + ] + }, + unblock: { + description: 'Re-allow a user or channel to triggering your highlights.', + type: ApplicationCommandOptionType.SubcommandGroup, + options: [ + { + name: 'user', + description: 'Re-allow a user to triggering your highlights', + start: 'What user/channel would you like to allow triggering your highlights again?', + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: 'target', + description: 'What user would you like to allow triggering your highlights again?', + retry: '{error} Enter a valid user or channel.', + type: ApplicationCommandOptionType.User, + resolve: 'Member', + required: true + } + ] + }, + { + name: 'channel', + description: 'Re-allow a channel to triggering your highlights.', + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: 'target', + description: 'What channel would you like to prevent from triggering your highlights?', + type: ApplicationCommandOptionType.Channel, + channelTypes: Constants.TextBasedChannelTypes, + required: true + } + ] + } + ] + }, + show: { + description: 'List all your current highlighted words.', + type: ApplicationCommandOptionType.Subcommand, + options: [] + }, + clear: { + description: 'Remove all of your highlighted words.', + type: ApplicationCommandOptionType.Subcommand, + options: [] + }, + matches: { + description: 'Test a phrase to see if it matches your current highlighted words.', + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: 'phrase', + description: 'What phrase would you like to test your highlighted words against?', + retry: '{error} Enter a valid phrase to test.', + type: ApplicationCommandOptionType.String, + required: true + } + ] + } +} as const); export default class HighlightCommand extends BushCommand { public constructor() { @@ -104,23 +162,9 @@ export default class HighlightCommand extends BushCommand { 'highlight clear', 'highlight matches I really like to eat bacon with my spaghetti' ], - slashOptions: Object.entries(highlightSubcommands).map((args) => { - // typescript being annoying - const [subcommand, description] = args as [keyof typeof highlightSubcommands, typeof args[1]]; - - return { - name: subcommand, - description, - type: ApplicationCommandOptionType.Subcommand, - options: highlightCommandArgs[subcommand].map((arg) => ({ - name: arg.name, - description: arg.description, - type: arg.type, - required: arg.required, - autocomplete: arg.autocomplete - })) - } as SlashOption; - }), + slashOptions: Object.entries(highlightSubcommands).map( + ([subcommand, options]) => ({ name: subcommand, ...options } as SlashOption) + ), slash: true, channel: 'guild', clientPermissions: (m) => clientSendAndPermCheck(m), @@ -143,9 +187,16 @@ export default class HighlightCommand extends BushCommand { return Flag.continue(`highlight-${subcommand}`); } - public override async execSlash(message: SlashMessage, args: { subcommand: keyof typeof highlightSubcommands }) { + public override async exec() { + throw new Error('This command is not meant to be executed directly.'); + } + + public override async execSlash( + message: SlashMessage, + args: { subcommand: keyof typeof highlightSubcommands; subcommandGroup?: string } + ) { // manual `Flag.continue` - const subcommand = this.handler.modules.get(`highlight-${args.subcommand}`)!; + const subcommand = this.handler.modules.get(`highlight-${args.subcommandGroup ?? args.subcommand}`)!; return subcommand.exec(message, args); } @@ -153,7 +204,7 @@ export default class HighlightCommand extends BushCommand { if (!interaction.inCachedGuild()) return interaction.respond([{ name: 'You must be in a server to use this command.', value: 'error' }]); - switch (interaction.options.getSubcommand(true)) { + switch (interaction.options.getSubcommandGroup(false) ?? interaction.options.getSubcommand(true)) { case 'word': { const { words } = (await Highlight.findOne({ where: { guild: interaction.guild.id, user: interaction.user.id } diff --git a/src/commands/utilities/highlight-add.ts b/src/commands/utilities/highlight-add.ts index 3547c90..facee2c 100644 --- a/src/commands/utilities/highlight-add.ts +++ b/src/commands/utilities/highlight-add.ts @@ -1,34 +1,34 @@ import { AllowedMentions, BushCommand, emojis, format, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; import assert from 'assert'; -import { highlightCommandArgs, highlightSubcommands } from './highlight-!.js'; +import { highlightSubcommands } from './highlight-!.js'; export default class HighlightAddCommand extends BushCommand { public constructor() { super('highlight-add', { aliases: [], category: 'utilities', - description: highlightSubcommands.add, + description: highlightSubcommands.add.description, args: [ { id: 'word', - description: highlightCommandArgs.add[0].description, + description: highlightSubcommands.add.options[0].description, type: 'string', match: 'rest', - prompt: highlightCommandArgs.add[0].description, - retry: highlightCommandArgs.add[0].retry, - optional: !highlightCommandArgs.add[0].required, + prompt: highlightSubcommands.add.options[0].description, + retry: highlightSubcommands.add.options[0].retry, + optional: !highlightSubcommands.add.options[0].required, only: 'text', slashType: false } - // { - // id: 'regex', - // description: highlightCommandArgs.add[1].description, - // match: 'flag', - // flag: '--regex', - // prompt: highlightCommandArgs.add[1].description, - // only: 'text', - // slashType: false - // } + /* { + id: 'regex', + description: highlightSubcommands.add.options[1].description, + match: 'flag', + flag: '--regex', + prompt: highlightSubcommands.add.options[1].description, + only: 'text', + slashType: false + } */ ], usage: [], examples: [], diff --git a/src/commands/utilities/highlight-block.ts b/src/commands/utilities/highlight-block.ts index 5429071..9ee8a5a 100644 --- a/src/commands/utilities/highlight-block.ts +++ b/src/commands/utilities/highlight-block.ts @@ -1,25 +1,16 @@ -import { - addToArray, - AllowedMentions, - Arg, - BushCommand, - emojis, - Highlight, - type ArgType, - type CommandMessage, - type SlashMessage -} from '#lib'; +import { AllowedMentions, BushCommand, emojis, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; import assert from 'assert'; import { Argument, ArgumentGeneratorReturn } from 'discord-akairo'; -import { Channel, GuildMember } from 'discord.js'; -import { highlightCommandArgs, highlightSubcommands } from './highlight-!.js'; +import { Channel, GuildMember, User } from 'discord.js'; +import { BlockResult } from '../../lib/common/HighlightManager.js'; +import { highlightSubcommands } from './highlight-!.js'; export default class HighlightBlockCommand extends BushCommand { public constructor() { super('highlight-block', { aliases: [], category: 'utilities', - description: highlightSubcommands.block, + description: highlightSubcommands.block.description, usage: [], examples: [], clientPermissions: [], @@ -28,24 +19,26 @@ export default class HighlightBlockCommand extends BushCommand { } public override *args(): ArgumentGeneratorReturn { - const target: ArgType<'member' | 'channel'> = yield { - type: Argument.union('member', 'channel'), + const target: ArgType<'member' | 'textBasedChannel'> = yield { + type: Argument.union('member', 'textBasedChannel'), match: 'rest', prompt: { - start: highlightCommandArgs.block[0].description, - retry: highlightCommandArgs.block[0].retry, - optional: !highlightCommandArgs.block[0].required + start: highlightSubcommands.block.options[0].start, + retry: highlightSubcommands.block.options[0].options[0].retry, + optional: !highlightSubcommands.block.options[0].options[0].required } }; return { target }; } - public override async exec(message: CommandMessage | SlashMessage, args: { target: string | ArgType<'member' | 'channel'> }) { + public override async exec(message: CommandMessage | SlashMessage, args: { target: ArgType<'member' | 'textBasedChannel'> }) { assert(message.inGuild()); - args.target = - typeof args.target === 'string' ? (await Arg.cast(Arg.union('member', 'channel'), message, args.target))! : args.target; + if (args.target instanceof User && message.util.isSlashMessage(message)) + args.target = message.interaction.options.getMember('target')!; + + if (!args.target) return message.util.reply(`${emojis.error} Could not resolve member.`); if (!args.target || !(args.target instanceof GuildMember || args.target instanceof Channel)) return await message.util.reply(`${emojis.error} You can only block users or channels.`); @@ -53,26 +46,21 @@ export default class HighlightBlockCommand extends BushCommand { if (args.target instanceof Channel && !args.target.isTextBased()) return await message.util.reply(`${emojis.error} You can only block text-based channels.`); - const [highlight] = await Highlight.findOrCreate({ - where: { guild: message.guild.id, user: message.author.id } - }); - - const key = `blacklisted${args.target instanceof Channel ? 'Channels' : 'Users'}` as const; + const res = await this.client.highlightManager.addBlock(message.guildId, message.author.id, args.target); - if (highlight[key].includes(args.target.id)) - return await message.util.reply({ - // eslint-disable-next-line @typescript-eslint/no-base-to-string - content: `${emojis.error} You have already blocked ${args.target}.`, - allowedMentions: AllowedMentions.none() - }); - - highlight[key] = addToArray(highlight[key], args.target.id); - await highlight.save(); + /* eslint-disable @typescript-eslint/no-base-to-string */ + const content = (() => { + switch (res) { + case BlockResult.ALREADY_BLOCKED: + return `${emojis.error} You have already blocked ${args.target}.`; + case BlockResult.ERROR: + return `${emojis.error} An error occurred while blocking ${args.target}.`; + case BlockResult.SUCCESS: + return `${emojis.success} Successfully blocked ${args.target} from triggering your highlights.`; + } + })(); + /* eslint-enable @typescript-eslint/no-base-to-string */ - return await message.util.reply({ - // eslint-disable-next-line @typescript-eslint/no-base-to-string - content: `${emojis.success} Successfully blocked ${args.target} from triggering your highlights.`, - allowedMentions: AllowedMentions.none() - }); + return await message.util.reply({ content, allowedMentions: AllowedMentions.none() }); } } diff --git a/src/commands/utilities/highlight-clear.ts b/src/commands/utilities/highlight-clear.ts index 9e1ed62..a5ff19e 100644 --- a/src/commands/utilities/highlight-clear.ts +++ b/src/commands/utilities/highlight-clear.ts @@ -7,7 +7,7 @@ export default class HighlightClearCommand extends BushCommand { super('highlight-clear', { aliases: [], category: 'utilities', - description: highlightSubcommands.clear, + description: highlightSubcommands.clear.description, usage: [], examples: [], clientPermissions: [], diff --git a/src/commands/utilities/highlight-matches.ts b/src/commands/utilities/highlight-matches.ts index 7bf94fd..8964af8 100644 --- a/src/commands/utilities/highlight-matches.ts +++ b/src/commands/utilities/highlight-matches.ts @@ -2,14 +2,14 @@ import { BushCommand, ButtonPaginator, chunk, colors, emojis, type ArgType, type import assert from 'assert'; import { type ArgumentGeneratorReturn } from 'discord-akairo'; import { type APIEmbed } from 'discord.js'; -import { highlightCommandArgs, highlightSubcommands } from './highlight-!.js'; +import { highlightSubcommands } from './highlight-!.js'; export default class HighlightMatchesCommand extends BushCommand { public constructor() { super('highlight-matches', { aliases: [], category: 'utilities', - description: highlightSubcommands.matches, + description: highlightSubcommands.matches.description, usage: [], examples: [], clientPermissions: [], @@ -22,9 +22,9 @@ export default class HighlightMatchesCommand extends BushCommand { type: 'string', match: 'rest', prompt: { - start: highlightCommandArgs.matches[0].description, - retry: highlightCommandArgs.matches[0].retry, - optional: !highlightCommandArgs.matches[0].required + start: highlightSubcommands.matches.options[0].description, + retry: highlightSubcommands.matches.options[0].retry, + optional: !highlightSubcommands.matches.options[0].required } }; diff --git a/src/commands/utilities/highlight-remove.ts b/src/commands/utilities/highlight-remove.ts index 4dddff6..67cf029 100644 --- a/src/commands/utilities/highlight-remove.ts +++ b/src/commands/utilities/highlight-remove.ts @@ -1,22 +1,22 @@ import { AllowedMentions, BushCommand, emojis, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; import assert from 'assert'; -import { highlightCommandArgs, highlightSubcommands } from './highlight-!.js'; +import { highlightSubcommands } from './highlight-!.js'; export default class HighlightRemoveCommand extends BushCommand { public constructor() { super('highlight-remove', { aliases: [], category: 'utilities', - description: highlightSubcommands.remove, + description: highlightSubcommands.remove.description, args: [ { id: 'word', - description: highlightCommandArgs.remove[0].description, + description: highlightSubcommands.remove.options[0].description, type: 'string', match: 'rest', - prompt: highlightCommandArgs.remove[0].description, - retry: highlightCommandArgs.remove[0].retry, - optional: !highlightCommandArgs.remove[0].required, + prompt: highlightSubcommands.remove.options[0].description, + retry: highlightSubcommands.remove.options[0].retry, + optional: !highlightSubcommands.remove.options[0].required, only: 'text', slashType: false } diff --git a/src/commands/utilities/highlight-show.ts b/src/commands/utilities/highlight-show.ts index 0558005..48b4971 100644 --- a/src/commands/utilities/highlight-show.ts +++ b/src/commands/utilities/highlight-show.ts @@ -8,7 +8,7 @@ export default class HighlightShowCommand extends BushCommand { super('highlight-show', { aliases: [], category: 'utilities', - description: highlightSubcommands.show, + description: highlightSubcommands.show.description, usage: [], examples: [], clientPermissions: [], @@ -19,9 +19,7 @@ export default class HighlightShowCommand extends BushCommand { public override async exec(message: CommandMessage | SlashMessage) { assert(message.inGuild()); - const [highlight] = await Highlight.findOrCreate({ - where: { guild: message.guild.id, user: message.author.id } - }); + const [highlight] = await Highlight.findOrCreate({ where: { guild: message.guild.id, user: message.author.id } }); void this.client.highlightManager.syncCache(); @@ -60,9 +58,6 @@ export default class HighlightShowCommand extends BushCommand { } ]); - return await message.util.reply({ - embeds: [embed], - allowedMentions: AllowedMentions.none() - }); + return await message.util.reply({ embeds: [embed], allowedMentions: AllowedMentions.none() }); } } diff --git a/src/commands/utilities/highlight-unblock.ts b/src/commands/utilities/highlight-unblock.ts index 7f416eb..d70fb28 100644 --- a/src/commands/utilities/highlight-unblock.ts +++ b/src/commands/utilities/highlight-unblock.ts @@ -1,24 +1,16 @@ -import { - AllowedMentions, - BushCommand, - emojis, - Highlight, - removeFromArray, - type ArgType, - type CommandMessage, - type SlashMessage -} from '#lib'; +import { AllowedMentions, BushCommand, emojis, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; import assert from 'assert'; import { Argument, ArgumentGeneratorReturn } from 'discord-akairo'; -import { Channel, GuildMember } from 'discord.js'; -import { highlightCommandArgs, highlightSubcommands } from './highlight-!.js'; +import { Channel, GuildMember, User } from 'discord.js'; +import { UnblockResult } from '../../lib/common/HighlightManager.js'; +import { highlightSubcommands } from './highlight-!.js'; export default class HighlightUnblockCommand extends BushCommand { public constructor() { super('highlight-unblock', { aliases: [], category: 'utilities', - description: highlightSubcommands.unblock, + description: highlightSubcommands.unblock.description, usage: [], examples: [], clientPermissions: [], @@ -27,46 +19,48 @@ export default class HighlightUnblockCommand extends BushCommand { } public override *args(): ArgumentGeneratorReturn { - const target: ArgType<'member' | 'channel'> = yield { - type: Argument.union('member', 'channel'), + const target: ArgType<'member' | 'textBasedChannel'> = yield { + type: Argument.union('member', 'textBasedChannel'), match: 'rest', prompt: { - start: highlightCommandArgs.unblock[0].description, - retry: highlightCommandArgs.unblock[0].retry, - optional: !highlightCommandArgs.unblock[0].required + start: highlightSubcommands.unblock.options[0].start, + retry: highlightSubcommands.unblock.options[0].options[0].retry, + optional: !highlightSubcommands.unblock.options[0].options[0].retry } }; return { target }; } - public override async exec(message: CommandMessage | SlashMessage, args: { target: ArgType<'user' | 'role' | 'member'> }) { + public override async exec(message: CommandMessage | SlashMessage, args: { target: ArgType<'member' | 'textBasedChannel'> }) { assert(message.inGuild()); + if (args.target instanceof User && message.util.isSlashMessage(message)) + args.target = message.interaction.options.getMember('target')!; + + if (!args.target) return message.util.reply(`${emojis.error} Could not resolve member.`); + if (!(args.target instanceof GuildMember || args.target instanceof Channel)) return await message.util.reply(`${emojis.error} You can only unblock users or channels.`); if (args.target instanceof Channel && !args.target.isTextBased()) return await message.util.reply(`${emojis.error} You can only unblock text-based channels.`); - const [highlight] = await Highlight.findOrCreate({ - where: { guild: message.guild.id, user: message.author.id } - }); - - const key = `blacklisted${args.target instanceof Channel ? 'Channels' : 'Users'}` as const; + const res = await this.client.highlightManager.removeBlock(message.guildId, message.author.id, args.target); - if (!highlight[key].includes(args.target.id)) - return await message.util.reply({ - content: `${emojis.error} ${args.target} is not blocked so cannot be unblock.`, - allowedMentions: AllowedMentions.none() - }); - - highlight[key] = removeFromArray(highlight[key], args.target.id); - await highlight.save(); + /* eslint-disable @typescript-eslint/no-base-to-string */ + const content = (() => { + switch (res) { + case UnblockResult.NOT_BLOCKED: + return `${emojis.error} ${args.target} is not blocked so cannot be unblock.`; + case UnblockResult.ERROR: + return `${emojis.error} An error occurred while unblocking ${args.target}.`; + case UnblockResult.SUCCESS: + return `${emojis.success} Successfully allowed ${args.target} to trigger your highlights.`; + } + })(); + /* eslint-enable @typescript-eslint/no-base-to-string */ - return await message.util.reply({ - content: `${emojis.success} Successfully allowed ${args.target} to trigger your highlights.`, - allowedMentions: AllowedMentions.none() - }); + return await message.util.reply({ content, allowedMentions: AllowedMentions.none() }); } } diff --git a/src/lib/common/HighlightManager.ts b/src/lib/common/HighlightManager.ts index cd89c89..c917110 100644 --- a/src/lib/common/HighlightManager.ts +++ b/src/lib/common/HighlightManager.ts @@ -1,6 +1,14 @@ import { addToArray, format, Highlight, removeFromArray, timestamp, type HighlightWord } from '#lib'; import assert from 'assert'; -import { Collection, type Message, type Snowflake } from 'discord.js'; +import { + Collection, + GuildMember, + type Channel, + type Client, + type Message, + type Snowflake, + type TextBasedChannel +} from 'discord.js'; import { colors, Time } from '../utils/BushConstants.js'; const NOTIFY_COOLDOWN = 5 * Time.Minute; @@ -47,6 +55,11 @@ export class HighlightManager { public readonly lastedDMedUserCooldown = new Collection<user, lastDM>(); /** + * @param client The client to use. + */ + public constructor(public client: Client) {} + + /** * Sync the cache with the database. */ public async syncCache(): Promise<void> { @@ -63,10 +76,10 @@ export class HighlightManager { }); if (!this.userBlocks.has(highlight.guild)) this.userBlocks.set(highlight.guild, new Collection()); - this.userBlocks.get(highlight.guild)!.set(highlight.user, new Set(...highlight.blacklistedUsers)); + this.userBlocks.get(highlight.guild)!.set(highlight.user, new Set(highlight.blacklistedUsers)); if (!this.channelBlocks.has(highlight.guild)) this.channelBlocks.set(highlight.guild, new Collection()); - this.channelBlocks.get(highlight.guild)!.set(highlight.user, new Set(...highlight.blacklistedChannels)); + this.channelBlocks.get(highlight.guild)!.set(highlight.user, new Set(highlight.blacklistedChannels)); } } @@ -90,10 +103,22 @@ export class HighlightManager { if (!message.channel.permissionsFor(user)?.has('ViewChannel')) continue; const blockedUsers = this.userBlocks.get(message.guildId)?.get(user) ?? new Set(); - if (blockedUsers.has(message.author.id)) continue; + if (blockedUsers.has(message.author.id)) { + void this.client.console.verbose( + 'Highlight', + `Highlight ignored because <<${user}>> blocked the user <<${message.author.id}>>` + ); + continue; + } const blockedChannels = this.channelBlocks.get(message.guildId)?.get(user) ?? new Set(); - if (blockedChannels.has(message.channel.id)) continue; + if (blockedChannels.has(message.channel.id)) { + void this.client.console.verbose( + 'Highlight', + `Highlight ignored because <<${user}>> blocked the channel <<${message.channel.id}>>` + ); + continue; + } ret.set(user, word); } @@ -219,6 +244,62 @@ export class HighlightManager { } /** + * Adds a new user or channel block to a user in a particular guild. + * @param guild The guild to add the block to. + * @param user The user that is blocking the target. + * @param target The target that is being blocked. + * @returns The result of the operation. + */ + public async addBlock(guild: Snowflake, user: Snowflake, target: GuildMember | TextBasedChannel): Promise<BlockResult> { + const cacheKey = `${target instanceof GuildMember ? 'user' : 'channel'}Blocks` as const; + const databaseKey = `blacklisted${target instanceof GuildMember ? 'Users' : 'Channels'}` as const; + + const [highlight] = await Highlight.findOrCreate({ where: { guild, user } }); + + if (highlight[databaseKey].includes(target.id)) return BlockResult.ALREADY_BLOCKED; + + const newBlocks = addToArray(highlight[databaseKey], target.id); + + highlight[databaseKey] = newBlocks; + const res = await highlight.save().catch(() => false); + if (!res) return BlockResult.ERROR; + + if (!this[cacheKey].has(guild)) this[cacheKey].set(guild, new Collection()); + const guildBlocks = this[cacheKey].get(guild)!; + guildBlocks.set(user, new Set(newBlocks)); + + return BlockResult.SUCCESS; + } + + /** + * Removes a user or channel block from a user in a particular guild. + * @param guild The guild to remove the block from. + * @param user The user that is unblocking the target. + * @param target The target that is being unblocked. + * @returns The result of the operation. + */ + public async removeBlock(guild: Snowflake, user: Snowflake, target: GuildMember | Channel): Promise<UnblockResult> { + const cacheKey = `${target instanceof GuildMember ? 'user' : 'channel'}Blocks` as const; + const databaseKey = `blacklisted${target instanceof GuildMember ? 'Users' : 'Channels'}` as const; + + const [highlight] = await Highlight.findOrCreate({ where: { guild, user } }); + + if (!highlight[databaseKey].includes(target.id)) return UnblockResult.NOT_BLOCKED; + + const newBlocks = removeFromArray(highlight[databaseKey], target.id); + + highlight[databaseKey] = newBlocks; + const res = await highlight.save().catch(() => false); + if (!res) return UnblockResult.ERROR; + + if (!this[cacheKey].has(guild)) this[cacheKey].set(guild, new Collection()); + const guildBlocks = this[cacheKey].get(guild)!; + guildBlocks.set(user, new Set(newBlocks)); + + return UnblockResult.SUCCESS; + } + + /** * Sends a user a direct message to alert them of their highlight being triggered. * @param message The message that triggered the highlight. * @param user The user who's highlights was triggered. @@ -232,7 +313,7 @@ export class HighlightManager { const lastDM = this.lastedDMedUserCooldown.get(user); if (!lastDM) break dmCooldown; - const cooldown = message.client.ownerID.includes(user) ? OWNER_NOTIFY_COOLDOWN : NOTIFY_COOLDOWN; + const cooldown = message.client.config.owners.includes(user) ? OWNER_NOTIFY_COOLDOWN : NOTIFY_COOLDOWN; if (new Date().getTime() - lastDM.getTime() < cooldown) { void message.client.console.verbose('Highlight', `User <<${user}>> has been dmed recently.`); @@ -310,3 +391,15 @@ export class HighlightManager { lastTalked.set(message.author.id, new Date()); } } + +export enum BlockResult { + ALREADY_BLOCKED, + ERROR, + SUCCESS +} + +export enum UnblockResult { + NOT_BLOCKED, + ERROR, + SUCCESS +} |