aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/commands/utilities/highlight-!.ts245
-rw-r--r--src/commands/utilities/highlight-add.ts30
-rw-r--r--src/commands/utilities/highlight-block.ts70
-rw-r--r--src/commands/utilities/highlight-clear.ts2
-rw-r--r--src/commands/utilities/highlight-matches.ts10
-rw-r--r--src/commands/utilities/highlight-remove.ts12
-rw-r--r--src/commands/utilities/highlight-show.ts11
-rw-r--r--src/commands/utilities/highlight-unblock.ts66
-rw-r--r--src/lib/common/HighlightManager.ts105
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
+}