From 2bcf6828b67a6eb3c2bbff3892ce5ca53d00e713 Mon Sep 17 00:00:00 2001 From: IRONM00N <64110067+IRONM00N@users.noreply.github.com> Date: Mon, 7 Feb 2022 23:55:54 -0500 Subject: started working on highlight command --- src/lib/common/HighlightManager.ts | 71 +++++++++++++++++++ src/lib/extensions/discord-akairo/BushClient.ts | 10 ++- .../extensions/discord-akairo/BushClientUtil.ts | 18 +++++ src/lib/index.ts | 1 + src/lib/models/instance/Highlight.ts | 81 ++++++++++++++++++++++ 5 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 src/lib/common/HighlightManager.ts create mode 100644 src/lib/models/instance/Highlight.ts (limited to 'src/lib') 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>> = new Map(); + public userLastTalkedCooldown = new Map>(); + public lastedDMedUserCooldown = new Map(); + + 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 { + // even if there are multiple matches, only the first one is returned + const ret = new Map(); + 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> { + const highlights = await Highlight.findAll({ where: { guild, user } }); + + const results = new Map(); + + 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'; @@ -183,6 +185,11 @@ export class BushClient extends AkairoClient extends AkairoClient>.`, 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 @@ -519,6 +519,24 @@ export class BushClientUtil extends ClientUtil { return [...set]; } + /** + * 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(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(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. 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 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 } + ); + } +} -- cgit