diff options
-rw-r--r-- | src/commands/info/help.ts | 5 | ||||
-rw-r--r-- | src/commands/utilities/highlight-!.ts | 150 | ||||
-rw-r--r-- | src/commands/utilities/highlight-add.ts | 82 | ||||
-rw-r--r-- | src/commands/utilities/highlight-block.ts | 69 | ||||
-rw-r--r-- | src/commands/utilities/highlight-clear.ts | 39 | ||||
-rw-r--r-- | src/commands/utilities/highlight-matches.ts | 0 | ||||
-rw-r--r-- | src/commands/utilities/highlight-remove.ts | 57 | ||||
-rw-r--r-- | src/commands/utilities/highlight-show.ts | 34 | ||||
-rw-r--r-- | src/commands/utilities/highlight-unblock.ts | 69 | ||||
-rw-r--r-- | src/lib/common/HighlightManager.ts | 71 | ||||
-rw-r--r-- | src/lib/extensions/discord-akairo/BushClient.ts | 10 | ||||
-rw-r--r-- | src/lib/extensions/discord-akairo/BushClientUtil.ts | 18 | ||||
-rw-r--r-- | src/lib/index.ts | 1 | ||||
-rw-r--r-- | src/lib/models/instance/Highlight.ts | 81 | ||||
-rw-r--r-- | src/listeners/message/highlight.ts | 15 |
15 files changed, 699 insertions, 2 deletions
diff --git a/src/commands/info/help.ts b/src/commands/info/help.ts index e31153b..2383566 100644 --- a/src/commands/info/help.ts +++ b/src/commands/info/help.ts @@ -77,7 +77,10 @@ export default class HelpCommand extends BushCommand { if (command.channel == 'guild' && !message.guild && !args.showHidden) return false; if (command.ownerOnly && !isOwner) return false; if (command.superUserOnly && !isSuperUser) return false; - return !(command.restrictedGuilds?.includes(message.guild?.id ?? '') === false && !args.showHidden); + if (command.restrictedGuilds?.includes(message.guild?.id ?? '') === false && !args.showHidden) return false; + if (command.aliases.length === 0) return false; + + return true; }); const categoryNice = category.id .replace(/(\b\w)/gi, (lc) => lc.toUpperCase()) diff --git a/src/commands/utilities/highlight-!.ts b/src/commands/utilities/highlight-!.ts new file mode 100644 index 0000000..332af03 --- /dev/null +++ b/src/commands/utilities/highlight-!.ts @@ -0,0 +1,150 @@ +import { BushCommand, Highlight, HighlightWord, type BushSlashMessage } from '#lib'; +import { Flag, type ArgumentGeneratorReturn, type SlashOption } from 'discord-akairo'; +import { ApplicationCommandOptionType } from 'discord-api-types'; +import { ApplicationCommandSubCommandData, AutocompleteInteraction, CacheType } from 'discord.js'; + +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.Mentionable, + 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.Mentionable, + 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 + } + ] +}; + +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 default class HighlightCommand extends BushCommand { + public constructor() { + super('highlight', { + aliases: ['highlight', 'hl'], + category: 'utilities', + description: 'Command description.', + usage: ['template <requiredArg> [optionalArg]'], + examples: ['template 1 2'], + 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; + }), + slash: true, + channel: 'guild', + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [], + ownerOnly: true + }); + } + + public override *args(): ArgumentGeneratorReturn { + const subcommand: keyof typeof highlightSubcommands = yield { + id: 'subcommand', + type: Object.keys(highlightSubcommands), + prompt: { + start: 'What sub command would you like to use?', + retry: `{error} Valid subcommands are: ${Object.keys(highlightSubcommands) + .map((s) => `\`${s}\``) + .join()}.` + } + }; + + return Flag.continue(`highlight-${subcommand}`); + } + + public override async execSlash(message: BushSlashMessage, args: { subcommand: keyof typeof highlightSubcommands }) { + // manual `Flag.continue` + const subcommand = this.handler.modules.get(`highlight-${args.subcommand}`)!; + return subcommand.exec(message, args); + } + + public override async autocomplete(interaction: AutocompleteInteraction<CacheType>) { + if (!interaction.inCachedGuild()) + return interaction.respond([{ name: 'You must be in a server to use this command.', value: 'error' }]); + + switch (interaction.options.getSubcommand(true)) { + case 'word': { + const { words } = (await Highlight.findOne({ + where: { + guild: interaction.guild.id, + user: interaction.user.id + } + })) ?? { words: [] as HighlightWord[] }; + if (!words.length) return interaction.respond([]); + return interaction.respond(words.map((w) => ({ name: w.word, value: w.word }))); + } + } + } +} diff --git a/src/commands/utilities/highlight-add.ts b/src/commands/utilities/highlight-add.ts new file mode 100644 index 0000000..ec5443c --- /dev/null +++ b/src/commands/utilities/highlight-add.ts @@ -0,0 +1,82 @@ +import { AllowedMentions, BushCommand, Highlight, type ArgType, type BushMessage, type BushSlashMessage } from '#lib'; +import assert from 'assert'; +import { ArgumentGeneratorReturn } from 'discord-akairo'; +import { highlightCommandArgs, highlightSubcommands } from './highlight-!'; + +export default class HighlightAddCommand extends BushCommand { + public constructor() { + super('highlight-add', { + aliases: [], + category: 'utilities', + description: highlightSubcommands.add, + usage: [], + examples: [], + clientPermissions: [], + userPermissions: [] + }); + } + + public override *args(): ArgumentGeneratorReturn { + const word: ArgType<'string'> = yield { + type: 'string', + match: 'rest', + prompt: { + start: highlightCommandArgs.add[0].description, + retry: highlightCommandArgs.add[0].retry, + optional: !highlightCommandArgs.add[0].required + } + }; + + const regex: boolean = yield { + match: 'flag', + flag: 'regex' + }; + + return { word, regex }; + } + + public override async exec( + message: BushMessage | BushSlashMessage, + args: { word: ArgType<'string'>; regex: ArgType<'boolean'> } + ) { + assert(message.inGuild()); + + if (!args.regex) { + if (args.word.length < 2) + return message.util.send(`${util.emojis.error} You can only highlight words that are longer than 2 characters.`); + if (args.word.length > 50) + return await message.util.reply(`${util.emojis.error} You can only highlight words that are shorter than 50 characters.`); + } else { + try { + new RegExp(args.word); + } catch (e) { + assert(e instanceof SyntaxError); + return message.util.send({ + content: `${util.emojis.error} Invalid regex ${util.format.inlineCode(e.message)}.`, + allowedMentions: AllowedMentions.none() + }); + } + } + + const [highlight] = await Highlight.findOrCreate({ + where: { + guild: message.guild.id, + user: message.author.id + } + }); + + if (highlight.words.some((w) => w.word === args.word)) + return await message.util.reply({ + content: `${util.emojis.error} You have already highlighted "${args.word}".`, + allowedMentions: AllowedMentions.none() + }); + + highlight.words = util.addToArray(highlight.words, { word: args.word, regex: args.regex }); + await highlight.save(); + + return await message.util.reply({ + content: `${util.emojis.success} Successfully added "${args.word}" to your highlight list.`, + allowedMentions: AllowedMentions.none() + }); + } +} diff --git a/src/commands/utilities/highlight-block.ts b/src/commands/utilities/highlight-block.ts new file mode 100644 index 0000000..5a18b8a --- /dev/null +++ b/src/commands/utilities/highlight-block.ts @@ -0,0 +1,69 @@ +import { AllowedMentions, BushCommand, Highlight, type ArgType, type BushMessage, type BushSlashMessage } from '#lib'; +import assert from 'assert'; +import { Argument, ArgumentGeneratorReturn } from 'discord-akairo'; +import { Channel, GuildMember } from 'discord.js'; +import { highlightCommandArgs, highlightSubcommands } from './highlight-!'; + +export default class HighlightBlockCommand extends BushCommand { + public constructor() { + super('highlight-block', { + aliases: [], + category: 'utilities', + description: highlightSubcommands.block, + usage: [], + examples: [], + clientPermissions: [], + userPermissions: [] + }); + } + + public override *args(): ArgumentGeneratorReturn { + const target: ArgType<'member'> | ArgType<'channel'> = yield { + type: Argument.union('member', 'channel'), + match: 'rest', + prompt: { + start: highlightCommandArgs.block[0].description, + retry: highlightCommandArgs.block[0].retry, + optional: !highlightCommandArgs.block[0].required + } + }; + + return { target }; + } + + public override async exec( + message: BushMessage | BushSlashMessage, + args: { target: ArgType<'user'> | ArgType<'role'> | ArgType<'member'> } + ) { + assert(message.inGuild()); + + if (!(args.target instanceof GuildMember || args.target instanceof Channel)) + return await message.util.reply(`${util.emojis.error} You can only block users or channels.`); + + if (args.target instanceof Channel && !args.target.isTextBased()) + return await message.util.reply(`${util.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; + + if (highlight[key].includes(args.target.id)) + return await message.util.reply({ + content: `${util.emojis.error} You have already blocked ${args.target}.`, + allowedMentions: AllowedMentions.none() + }); + + highlight[key] = util.addToArray(highlight[key], args.target.id); + await highlight.save(); + + return await message.util.reply({ + content: `${util.emojis.success} Successfully blocked ${args.target} from triggering your highlights.`, + allowedMentions: AllowedMentions.none() + }); + } +} diff --git a/src/commands/utilities/highlight-clear.ts b/src/commands/utilities/highlight-clear.ts new file mode 100644 index 0000000..aded467 --- /dev/null +++ b/src/commands/utilities/highlight-clear.ts @@ -0,0 +1,39 @@ +import { AllowedMentions, BushCommand, ConfirmationPrompt, Highlight, type BushMessage, type BushSlashMessage } from '#lib'; +import assert from 'assert'; +import { highlightSubcommands } from './highlight-!'; + +export default class HighlightClearCommand extends BushCommand { + public constructor() { + super('highlight-clear', { + aliases: [], + category: 'utilities', + description: highlightSubcommands.clear, + usage: [], + examples: [], + clientPermissions: [], + userPermissions: [] + }); + } + + public override async exec(message: BushMessage | BushSlashMessage) { + assert(message.inGuild()); + + const [highlight] = await Highlight.findOrCreate({ + where: { + guild: message.guild.id, + user: message.author.id + } + }); + + const confirm = await ConfirmationPrompt.send(message, { content: `Are you sure you want to clear your highlight list?` }); + if (!confirm) return await message.util.reply(`${util.emojis.warn} You decided not to clear your highlight list.`); + + highlight.words = []; + await highlight.save(); + + return await message.util.reply({ + content: `${util.emojis.success} Successfully cleared your highlight list.`, + allowedMentions: AllowedMentions.none() + }); + } +} diff --git a/src/commands/utilities/highlight-matches.ts b/src/commands/utilities/highlight-matches.ts new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/commands/utilities/highlight-matches.ts diff --git a/src/commands/utilities/highlight-remove.ts b/src/commands/utilities/highlight-remove.ts new file mode 100644 index 0000000..0432a16 --- /dev/null +++ b/src/commands/utilities/highlight-remove.ts @@ -0,0 +1,57 @@ +import { AllowedMentions, BushCommand, Highlight, type ArgType, type BushMessage, type BushSlashMessage } from '#lib'; +import assert from 'assert'; +import { ArgumentGeneratorReturn } from 'discord-akairo'; +import { highlightCommandArgs, highlightSubcommands } from './highlight-!'; + +export default class HighlightRemoveCommand extends BushCommand { + public constructor() { + super('highlight-remove', { + aliases: [], + category: 'utilities', + description: highlightSubcommands.remove, + usage: [], + examples: [], + clientPermissions: [], + userPermissions: [] + }); + } + + public override *args(): ArgumentGeneratorReturn { + const word: ArgType<'string'> = yield { + type: 'string', + match: 'rest', + prompt: { + start: highlightCommandArgs.remove[0].description, + retry: highlightCommandArgs.remove[0].retry, + optional: !highlightCommandArgs.remove[0].required + } + }; + + return { word }; + } + + public override async exec(message: BushMessage | BushSlashMessage, args: { word: ArgType<'string'> }) { + assert(message.inGuild()); + + const [highlight] = await Highlight.findOrCreate({ + where: { + guild: message.guild.id, + user: message.author.id + } + }); + + if (!highlight.words.some((w) => w.word === args.word)) + return await message.util.reply({ + content: `${util.emojis.error} You have not highlighted "${args.word}".`, + allowedMentions: AllowedMentions.none() + }); + + highlight.words = util.removeFromArray(highlight.words, highlight.words.find((w) => w.word === args.word)!); + await highlight.save(); + + return await message.util.reply({ + content: `${util.emojis.success} Successfully removed "${args.word}" from your highlight list.`, + allowedMentions: AllowedMentions.none() + }); + } +} diff --git a/src/commands/utilities/highlight-show.ts b/src/commands/utilities/highlight-show.ts new file mode 100644 index 0000000..ab7c0c5 --- /dev/null +++ b/src/commands/utilities/highlight-show.ts @@ -0,0 +1,34 @@ +import { AllowedMentions, BushCommand, Highlight, type BushMessage, type BushSlashMessage } from '#lib'; +import assert from 'assert'; +import { Embed } from 'discord.js'; +import { highlightSubcommands } from './highlight-!'; + +export default class HighlightShowCommand extends BushCommand { + public constructor() { + super('highlight-show', { + aliases: [], + category: 'utilities', + description: highlightSubcommands.show, + usage: [], + examples: [], + clientPermissions: [], + userPermissions: [] + }); + } + + public override async exec(message: BushMessage | BushSlashMessage) { + assert(message.inGuild()); + + const [highlight] = await Highlight.findOrCreate({ + where: { + guild: message.guild.id, + user: message.author.id + } + }); + + return await message.util.reply({ + embeds: [new Embed().setTitle('Highlight List').setDescription(highlight.words.join('\n')).setColor(util.colors.default)], + allowedMentions: AllowedMentions.none() + }); + } +} diff --git a/src/commands/utilities/highlight-unblock.ts b/src/commands/utilities/highlight-unblock.ts new file mode 100644 index 0000000..7e5c0fb --- /dev/null +++ b/src/commands/utilities/highlight-unblock.ts @@ -0,0 +1,69 @@ +import { AllowedMentions, BushCommand, Highlight, type ArgType, type BushMessage, type BushSlashMessage } from '#lib'; +import assert from 'assert'; +import { Argument, ArgumentGeneratorReturn } from 'discord-akairo'; +import { Channel, GuildMember } from 'discord.js'; +import { highlightCommandArgs, highlightSubcommands } from './highlight-!'; + +export default class HighlightUnblockCommand extends BushCommand { + public constructor() { + super('highlight-unblock', { + aliases: [], + category: 'utilities', + description: highlightSubcommands.unblock, + usage: [], + examples: [], + clientPermissions: [], + userPermissions: [] + }); + } + + public override *args(): ArgumentGeneratorReturn { + const target: ArgType<'member'> | ArgType<'channel'> = yield { + type: Argument.union('member', 'channel'), + match: 'rest', + prompt: { + start: highlightCommandArgs.unblock[0].description, + retry: highlightCommandArgs.unblock[0].retry, + optional: !highlightCommandArgs.unblock[0].required + } + }; + + return { target }; + } + + public override async exec( + message: BushMessage | BushSlashMessage, + args: { target: ArgType<'user'> | ArgType<'role'> | ArgType<'member'> } + ) { + assert(message.inGuild()); + + if (!(args.target instanceof GuildMember || args.target instanceof Channel)) + return await message.util.reply(`${util.emojis.error} You can only unblock users or channels.`); + + if (args.target instanceof Channel && !args.target.isTextBased()) + return await message.util.reply(`${util.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; + + if (!highlight[key].includes(args.target.id)) + return await message.util.reply({ + content: `${util.emojis.error} ${args.target} is not blocked so cannot be unblock.`, + allowedMentions: AllowedMentions.none() + }); + + highlight[key] = util.removeFromArray(highlight[key], args.target.id); + await highlight.save(); + + return await message.util.reply({ + content: `${util.emojis.success} Successfully blocked ${args.target} from triggering your highlights.`, + allowedMentions: AllowedMentions.none() + }); + } +} diff --git a/src/lib/common/HighlightManager.ts b/src/lib/common/HighlightManager.ts new file mode 100644 index 0000000..a74ce9e --- /dev/null +++ b/src/lib/common/HighlightManager.ts @@ -0,0 +1,71 @@ +import { Highlight, type BushMessage, type HighlightWord } from '#lib'; +import type { Snowflake } from 'discord.js'; + +export class HighlightManager { + public cachedHighlights: Map</* guild */ Snowflake, Map</* word */ HighlightWord, /* users */ Set<Snowflake>>> = new Map(); + public userLastTalkedCooldown = new Map<Snowflake, Map<Snowflake, Date>>(); + public lastedDMedUserCooldown = new Map</* user */ Snowflake, /* last dm */ Date>(); + + public async syncCache() { + const highlights = await Highlight.findAll(); + + this.cachedHighlights.clear(); + + for (const highlight of highlights) { + highlight.words.forEach((word) => { + if (!this.cachedHighlights.has(highlight.guild)) this.cachedHighlights.set(highlight.guild, new Map()); + const guildCache = this.cachedHighlights.get(highlight.guild)!; + if (!guildCache.get(word)) guildCache.set(word, new Set()); + guildCache.get(word)!.add(highlight.user); + }); + } + } + + public checkMessage(message: BushMessage): Map<Snowflake, string> { + // even if there are multiple matches, only the first one is returned + const ret = new Map<Snowflake, string>(); + if (!message.content || !message.inGuild()) return ret; + if (!this.cachedHighlights.has(message.guildId)) return ret; + + const guildCache = this.cachedHighlights.get(message.guildId)!; + + for (const [word, users] of guildCache.entries()) { + if (this.isMatch(message.content, word)) { + for (const user of users) { + if (!ret.has(user)) ret.set(user, word.word); + } + } + } + + return ret; + } + + public async checkPhraseForUser(guild: Snowflake, user: Snowflake, phrase: string): Promise<Map<string, boolean>> { + const highlights = await Highlight.findAll({ where: { guild, user } }); + + const results = new Map<string, boolean>(); + + for (const highlight of highlights) { + for (const word of highlight.words) { + if (this.isMatch(phrase, word)) { + results.set(word.word, true); + } + } + } + + return results; + } + + private isMatch(phrase: string, word: HighlightWord) { + if (word.regex) { + return new RegExp(word.word, 'gi').test(phrase); + } else { + if (word.word.includes(' ')) { + return phrase.includes(word.word); + } else { + const words = phrase.split(/\s*\b\s/); + return words.includes(word.word); + } + } + } +} diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts index eb1fe88..3f1c944 100644 --- a/src/lib/extensions/discord-akairo/BushClient.ts +++ b/src/lib/extensions/discord-akairo/BushClient.ts @@ -10,7 +10,7 @@ import { roleWithDuration, snowflake } from '#args'; -import type { +import { BushBaseGuildEmojiManager, BushChannelManager, BushClientEvents, @@ -47,8 +47,10 @@ import type { Options as SequelizeOptions, Sequelize as SequelizeType } from 'se import { fileURLToPath } from 'url'; import UpdateCacheTask from '../../../tasks/updateCache.js'; import UpdateStatsTask from '../../../tasks/updateStats.js'; +import { HighlightManager } from '../../common/HighlightManager'; import { ActivePunishment } from '../../models/instance/ActivePunishment.js'; import { Guild as GuildModel } from '../../models/instance/Guild.js'; +import { Highlight } from '../../models/instance/Highlight.js'; import { Level } from '../../models/instance/Level.js'; import { ModLog } from '../../models/instance/ModLog.js'; import { Reminder } from '../../models/instance/Reminder.js'; @@ -184,6 +186,11 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re public sentry!: typeof Sentry; /** + * Manages most aspects of the highlight command + */ + public highlightManager = new HighlightManager(); + + /** * @param config The configuration for the bot. */ public constructor(config: Config) { @@ -403,6 +410,7 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re Level.initModel(this.instanceDB); StickyRole.initModel(this.instanceDB); Reminder.initModel(this.instanceDB); + Highlight.initModel(this.instanceDB); await this.instanceDB.sync({ alter: true }); // Sync all tables to fix everything if updated await this.console.success('startup', `Successfully connected to <<instance database>>.`, false); } catch (e) { diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index c3739d6..a3ddfed 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -520,6 +520,24 @@ export class BushClientUtil extends ClientUtil { } /** + * Remove an item from an array. All duplicates will be removed. + * @param array The array to remove an element from. + * @param value The element to remove from the array. + */ + public removeFromArray<T>(array: T[], value: T): T[] { + return this.addOrRemoveFromArray('remove', array, value); + } + + /** + * Add an item from an array. All duplicates will be removed. + * @param array The array to add an element to. + * @param value The element to add to the array. + */ + public addToArray<T>(array: T[], value: T): T[] { + return this.addOrRemoveFromArray('add', array, value); + } + + /** * Surrounds a string to the begging an end of each element in an array. * @param array The array you want to surround. * @param surroundChar1 The character placed in the beginning of the element. diff --git a/src/lib/index.ts b/src/lib/index.ts index 0c73875..7a9ab5f 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -69,6 +69,7 @@ export * from './extensions/discord.js/other.js'; export * from './models/BaseModel.js'; export * from './models/instance/ActivePunishment.js'; export * from './models/instance/Guild.js'; +export * from './models/instance/Highlight.js'; export * from './models/instance/Level.js'; export * from './models/instance/ModLog.js'; export * from './models/instance/Reminder.js'; diff --git a/src/lib/models/instance/Highlight.ts b/src/lib/models/instance/Highlight.ts new file mode 100644 index 0000000..5889fad --- /dev/null +++ b/src/lib/models/instance/Highlight.ts @@ -0,0 +1,81 @@ +import { type Snowflake } from 'discord.js'; +import { nanoid } from 'nanoid'; +import { type Sequelize } from 'sequelize'; +import { BaseModel } from '../BaseModel.js'; +const { DataTypes } = (await import('sequelize')).default; + +export interface HighlightModel { + pk: string; + user: Snowflake; + guild: Snowflake; + words: HighlightWord[]; + blacklistedChannels: Snowflake[]; + blacklistedUsers: Snowflake[]; +} + +export interface HighLightCreationAttributes { + pk?: string; + user: Snowflake; + guild: Snowflake; + words?: HighlightWord[]; + blacklistedChannels?: Snowflake[]; + blacklistedUsers?: Snowflake[]; +} + +export interface HighlightWord { + word: string; + regex: boolean; +} + +/** + * List of words that should cause the user to be notified for if found in the specified guild. + */ +export class Highlight extends BaseModel<HighlightModel, HighLightCreationAttributes> implements HighlightModel { + /** + * The primary key of the highlight. + */ + public declare pk: string; + + /** + * The user that the highlight is for. + */ + public declare user: Snowflake; + + /** + * The guild to look for highlights in. + */ + public declare guild: Snowflake; + + /** + * The words to look for. + */ + public declare words: HighlightWord[]; + + /** + * Channels that the user choose to ignore highlights in. + */ + public declare blacklistedChannels: Snowflake[]; + + /** + * Users that the user choose to ignore highlights from. + */ + public declare blacklistedUsers: Snowflake[]; + + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ + public static initModel(sequelize: Sequelize): void { + Highlight.init( + { + pk: { type: DataTypes.STRING, primaryKey: true, defaultValue: nanoid }, + user: { type: DataTypes.STRING, allowNull: false }, + guild: { type: DataTypes.STRING, allowNull: false }, + words: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + blacklistedChannels: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + blacklistedUsers: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] } + }, + { sequelize } + ); + } +} diff --git a/src/listeners/message/highlight.ts b/src/listeners/message/highlight.ts new file mode 100644 index 0000000..25c8364 --- /dev/null +++ b/src/listeners/message/highlight.ts @@ -0,0 +1,15 @@ +import { BushListener, type BushClientEvents } from '#lib'; + +export default class HighlightListener extends BushListener { + public constructor() { + super('highlight', { + emitter: 'client', + event: 'messageCreate', + category: 'message' + }); + } + + public override async exec(...[message]: BushClientEvents['messageCreate']) { + if (!message.inGuild()) return; + } +} |