diff options
Diffstat (limited to 'lib/models')
-rw-r--r-- | lib/models/BaseModel.ts | 13 | ||||
-rw-r--r-- | lib/models/instance/ActivePunishment.ts | 94 | ||||
-rw-r--r-- | lib/models/instance/Guild.ts | 431 | ||||
-rw-r--r-- | lib/models/instance/Highlight.ts | 81 | ||||
-rw-r--r-- | lib/models/instance/Level.ts | 70 | ||||
-rw-r--r-- | lib/models/instance/ModLog.ts | 127 | ||||
-rw-r--r-- | lib/models/instance/Reminder.ts | 84 | ||||
-rw-r--r-- | lib/models/instance/StickyRole.ts | 58 | ||||
-rw-r--r-- | lib/models/shared/Global.ts | 67 | ||||
-rw-r--r-- | lib/models/shared/GuildCount.ts | 38 | ||||
-rw-r--r-- | lib/models/shared/MemberCount.ts | 37 | ||||
-rw-r--r-- | lib/models/shared/Shared.ts | 84 | ||||
-rw-r--r-- | lib/models/shared/Stat.ts | 72 |
13 files changed, 1256 insertions, 0 deletions
diff --git a/lib/models/BaseModel.ts b/lib/models/BaseModel.ts new file mode 100644 index 0000000..8fba5e5 --- /dev/null +++ b/lib/models/BaseModel.ts @@ -0,0 +1,13 @@ +import { Model } from 'sequelize'; + +export abstract class BaseModel<A, B> extends Model<A, B> { + /** + * The date when the row was created. + */ + public declare readonly createdAt: Date; + + /** + * The date when the row was last updated. + */ + public declare readonly updatedAt: Date; +} diff --git a/lib/models/instance/ActivePunishment.ts b/lib/models/instance/ActivePunishment.ts new file mode 100644 index 0000000..38012ca --- /dev/null +++ b/lib/models/instance/ActivePunishment.ts @@ -0,0 +1,94 @@ +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 enum ActivePunishmentType { + BAN = 'BAN', + MUTE = 'MUTE', + ROLE = 'ROLE', + BLOCK = 'BLOCK' +} + +export interface ActivePunishmentModel { + id: string; + type: ActivePunishmentType; + user: Snowflake; + guild: Snowflake; + extraInfo: Snowflake; + expires: Date | null; + modlog: string; +} + +export interface ActivePunishmentModelCreationAttributes { + id?: string; + type: ActivePunishmentType; + user: Snowflake; + guild: Snowflake; + extraInfo?: Snowflake; + expires?: Date; + modlog: string; +} + +/** + * Keeps track of active punishments so they can be removed later. + */ +export class ActivePunishment + extends BaseModel<ActivePunishmentModel, ActivePunishmentModelCreationAttributes> + implements ActivePunishmentModel +{ + /** + * The ID of this punishment (no real use just for a primary key) + */ + public declare id: string; + + /** + * The type of punishment. + */ + public declare type: ActivePunishmentType; + + /** + * The user who is punished. + */ + public declare user: Snowflake; + + /** + * The guild they are punished in. + */ + public declare guild: Snowflake; + + /** + * Additional info about the punishment if applicable. The channel id for channel blocks and role for punishment roles. + */ + public declare extraInfo: Snowflake; + + /** + * The date when this punishment expires (optional). + */ + public declare expires: Date | null; + + /** + * The reference to the modlog entry. + */ + public declare modlog: string; + + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ + public static initModel(sequelize: Sequelize): void { + ActivePunishment.init( + { + id: { type: DataTypes.STRING, primaryKey: true, defaultValue: nanoid }, + type: { type: DataTypes.STRING, allowNull: false }, + user: { type: DataTypes.STRING, allowNull: false }, + guild: { type: DataTypes.STRING, allowNull: false, references: { model: 'Guilds', key: 'id' } }, + extraInfo: { type: DataTypes.STRING, allowNull: true }, + expires: { type: DataTypes.DATE, allowNull: true }, + modlog: { type: DataTypes.STRING, allowNull: true, references: { model: 'ModLogs', key: 'id' } } + }, + { sequelize } + ); + } +} diff --git a/lib/models/instance/Guild.ts b/lib/models/instance/Guild.ts new file mode 100644 index 0000000..f258d48 --- /dev/null +++ b/lib/models/instance/Guild.ts @@ -0,0 +1,431 @@ +import { ChannelType, Constants, type Snowflake } from 'discord.js'; +import { type Sequelize } from 'sequelize'; +import { BadWordDetails } from '../../automod/AutomodShared.js'; +import { type BushClient } from '../../extensions/discord-akairo/BushClient.js'; +import { BaseModel } from '../BaseModel.js'; +const { DataTypes } = (await import('sequelize')).default; + +export interface GuildModel { + id: Snowflake; + prefix: string; + autoPublishChannels: Snowflake[]; + blacklistedChannels: Snowflake[]; + blacklistedUsers: Snowflake[]; + welcomeChannel: Snowflake | null; + muteRole: Snowflake | null; + punishmentEnding: string | null; + disabledCommands: string[]; + lockdownChannels: Snowflake[]; + autoModPhases: BadWordDetails[]; + enabledFeatures: GuildFeatures[]; + joinRoles: Snowflake[]; + logChannels: LogChannelDB; + bypassChannelBlacklist: Snowflake[]; + noXpChannels: Snowflake[]; + levelRoles: { [level: number]: Snowflake }; + levelUpChannel: Snowflake | null; +} + +export interface GuildModelCreationAttributes { + id: Snowflake; + prefix?: string; + autoPublishChannels?: Snowflake[]; + blacklistedChannels?: Snowflake[]; + blacklistedUsers?: Snowflake[]; + welcomeChannel?: Snowflake; + muteRole?: Snowflake; + punishmentEnding?: string; + disabledCommands?: string[]; + lockdownChannels?: Snowflake[]; + autoModPhases?: BadWordDetails[]; + enabledFeatures?: GuildFeatures[]; + joinRoles?: Snowflake[]; + logChannels?: LogChannelDB; + bypassChannelBlacklist?: Snowflake[]; + noXpChannels?: Snowflake[]; + levelRoles?: { [level: number]: Snowflake }; + levelUpChannel?: Snowflake; +} + +/** + * Settings for a guild. + */ +export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> implements GuildModel { + /** + * The ID of the guild + */ + public declare id: Snowflake; + + /** + * The bot's prefix for the guild + */ + public declare prefix: string; + + /** + * Channels that will have their messages automatically published + */ + public declare autoPublishChannels: Snowflake[]; + + /** + * Channels where the bot won't respond in. + */ + public declare blacklistedChannels: Snowflake[]; + + /** + * Users that the bot ignores in this guild + */ + public declare blacklistedUsers: Snowflake[]; + + /** + * The channels where the welcome messages are sent + */ + public declare welcomeChannel: Snowflake | null; + + /** + * The role given out when muting someone + */ + public declare muteRole: Snowflake | null; + + /** + * The message that gets sent after someone gets a punishment dm + */ + public declare punishmentEnding: string | null; + + /** + * Guild specific disabled commands + */ + public declare disabledCommands: string[]; + + /** + * Channels that should get locked down when the lockdown command gets used. + */ + public declare lockdownChannels: Snowflake[]; + + /** + * Custom automod phases + */ + public declare autoModPhases: BadWordDetails[]; + + /** + * The features enabled in a guild + */ + public declare enabledFeatures: GuildFeatures[]; + + /** + * The roles to assign to a user if they are not assigned sticky roles + */ + public declare joinRoles: Snowflake[]; + + /** + * The channels where logging messages will be sent. + */ + public declare logChannels: LogChannelDB; + + /** + * These users will be able to use commands in channels blacklisted + */ + public declare bypassChannelBlacklist: Snowflake[]; + + /** + * Channels where users will not earn xp for leveling. + */ + public declare noXpChannels: Snowflake[]; + + /** + * What roles get given to users when they reach certain levels. + */ + public declare levelRoles: { [level: number]: Snowflake }; + + /** + * The channel to send level up messages in instead of last channel. + */ + public declare levelUpChannel: Snowflake | null; + + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ + public static initModel(sequelize: Sequelize, client: BushClient): void { + Guild.init( + { + id: { type: DataTypes.STRING, primaryKey: true }, + prefix: { type: DataTypes.TEXT, allowNull: false, defaultValue: client.config.prefix }, + autoPublishChannels: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + blacklistedChannels: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + blacklistedUsers: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + welcomeChannel: { type: DataTypes.STRING, allowNull: true }, + muteRole: { type: DataTypes.STRING, allowNull: true }, + punishmentEnding: { type: DataTypes.TEXT, allowNull: true }, + disabledCommands: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + lockdownChannels: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + autoModPhases: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + enabledFeatures: { + type: DataTypes.JSONB, + allowNull: false, + defaultValue: Object.keys(guildFeaturesObj).filter( + (key) => guildFeaturesObj[key as keyof typeof guildFeaturesObj].default + ) + }, + joinRoles: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + logChannels: { type: DataTypes.JSONB, allowNull: false, defaultValue: {} }, + bypassChannelBlacklist: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + noXpChannels: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + levelRoles: { type: DataTypes.JSONB, allowNull: false, defaultValue: {} }, + levelUpChannel: { type: DataTypes.STRING, allowNull: true } + }, + { sequelize } + ); + } +} + +export type BaseGuildSetting = 'channel' | 'role' | 'user'; +export type GuildNoArraySetting = 'string' | 'custom' | BaseGuildSetting; +export type GuildSettingType = GuildNoArraySetting | `${BaseGuildSetting}-array`; + +export interface GuildSetting { + name: string; + description: string; + type: GuildSettingType; + subType: ChannelType[] | undefined; + configurable: boolean; + replaceNullWith: () => string | null; +} +const asGuildSetting = <T>(et: { [K in keyof T]: PartialBy<GuildSetting, 'configurable' | 'subType' | 'replaceNullWith'> }) => { + for (const key in et) { + et[key].subType ??= undefined; + et[key].configurable ??= true; + et[key].replaceNullWith ??= () => null; + } + return et as { [K in keyof T]: GuildSetting }; +}; + +const { default: config } = await import('../../../config/options.js'); + +export const guildSettingsObj = asGuildSetting({ + prefix: { + name: 'Prefix', + description: 'The phrase required to trigger text commands in this server.', + type: 'string', + replaceNullWith: () => config.prefix + }, + autoPublishChannels: { + name: 'Auto Publish Channels', + description: 'Channels were every message is automatically published.', + type: 'channel-array', + subType: [ChannelType.GuildNews] + }, + welcomeChannel: { + name: 'Welcome Channel', + description: 'The channel where the bot will send join and leave message.', + type: 'channel', + subType: [ + ChannelType.GuildText, + ChannelType.GuildNews, + ChannelType.GuildNewsThread, + ChannelType.GuildPublicThread, + ChannelType.GuildPrivateThread + ] + }, + muteRole: { + name: 'Mute Role', + description: 'The role assigned when muting someone.', + type: 'role' + }, + punishmentEnding: { + name: 'Punishment Ending', + description: 'The message after punishment information to a user in a dm.', + type: 'string' + }, + lockdownChannels: { + name: 'Lockdown Channels', + description: 'Channels that are locked down when a mass lockdown is specified.', + type: 'channel-array', + subType: [ChannelType.GuildText] + }, + joinRoles: { + name: 'Join Roles', + description: 'Roles assigned to users on join who do not have sticky role information.', + type: 'role-array' + }, + bypassChannelBlacklist: { + name: 'Bypass Channel Blacklist', + description: 'These users will be able to use commands in channels blacklisted.', + type: 'user-array' + }, + logChannels: { + name: 'Log Channels', + description: 'The channel were logs are sent.', + type: 'custom', + subType: [ChannelType.GuildText], + configurable: false + }, + autoModPhases: { + name: 'Automod Phases', + description: 'Custom phrases to be detected by automod.', + type: 'custom', + configurable: false + }, + noXpChannels: { + name: 'No Xp Channels', + description: 'Channels where users will not earn xp for leveling.', + type: 'channel-array', + subType: Constants.TextBasedChannelTypes.filter((type) => type !== ChannelType.DM) + }, + levelRoles: { + name: 'Level Roles', + description: 'What roles get given to users when they reach certain levels.', + type: 'custom', + configurable: false + }, + levelUpChannel: { + name: 'Level Up Channel', + description: 'The channel to send level up messages in instead of last channel.', + type: 'channel', + subType: Constants.TextBasedChannelTypes.filter((type) => type !== ChannelType.DM) + } +}); + +export type GuildSettings = keyof typeof guildSettingsObj; +export const settingsArr = Object.keys(guildSettingsObj).filter( + (s) => guildSettingsObj[s as GuildSettings].configurable +) as GuildSettings[]; + +interface GuildFeature { + name: string; + description: string; + default: boolean; + hidden: boolean; +} + +type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>; + +const asGuildFeature = <T>(gf: { [K in keyof T]: PartialBy<GuildFeature, 'hidden' | 'default'> }): { + [K in keyof T]: GuildFeature; +} => { + for (const key in gf) { + gf[key].hidden ??= false; + gf[key].default ??= false; + } + return gf as { [K in keyof T]: GuildFeature }; +}; + +export const guildFeaturesObj = asGuildFeature({ + automod: { + name: 'Automod', + description: 'Deletes offensive content as well as phishing links.' + }, + excludeDefaultAutomod: { + name: 'Exclude Default Automod', + description: 'Opt out of using the default automod options.' + }, + excludeAutomodScamLinks: { + name: 'Exclude Automod Scam Links', + description: 'Opt out of having automod delete scam links.' + }, + delScamMentions: { + name: 'Delete Scam Mentions', + description: 'Deletes messages with @everyone and @here mentions that have common scam phrases.' + }, + automodPresence: { + name: 'Automod Presence', + description: 'Logs presence changes that trigger automod.', + hidden: true + }, + automodMembers: { + name: 'Automod Members', + description: "Logs members' usernames and nicknames changes if they match automod." + }, + blacklistedFile: { + name: 'Blacklisted File', + description: 'Automatically deletes malicious files.' + }, + autoPublish: { + name: 'Auto Publish', + description: 'Publishes messages in configured announcement channels.' + }, + // todo implement a better auto thread system + autoThread: { + name: 'Auto Thread', + description: 'Creates a new thread for messages in configured channels.', + hidden: true + }, + perspectiveApi: { + name: 'Perspective API', + description: 'Use the Perspective API to detect toxicity.', + hidden: true + }, + boosterMessageReact: { + name: 'Booster Message React', + description: 'Reacts to booster messages with the boost emoji.' + }, + leveling: { + name: 'Leveling', + description: "Tracks users' messages and assigns them xp." + }, + sendLevelUpMessages: { + name: 'Send Level Up Messages', + description: 'Send a message when a user levels up.', + default: true + }, + stickyRoles: { + name: 'Sticky Roles', + description: 'Restores past roles to a user when they rejoin.' + }, + reporting: { + name: 'Reporting', + description: 'Allow users to make reports.' + }, + modsCanPunishMods: { + name: 'Mods Can Punish Mods', + description: 'Allow moderators to punish other moderators.' + }, + logManualPunishments: { + name: 'Log Manual Punishments', + description: "Adds manual punishment to the user's modlogs and the logging channels.", + default: true + }, + punishmentAppeals: { + name: 'Punishment Appeals', + description: 'Allow users to appeal their punishments and send the appeal to the configured channel.', + hidden: true + }, + highlight: { + name: 'Highlight', + description: 'Allows the highlight command to be used.', + default: true + } +}); + +export const guildLogsObj = { + automod: { + description: 'Sends a message in this channel every time automod is activated.', + configurable: true + }, + moderation: { + description: 'Sends a message in this channel every time a moderation action is performed.', + configurable: true + }, + report: { + description: 'Logs user reports.', + configurable: true + }, + error: { + description: 'Logs errors that occur with the bot.', + configurable: true + }, + appeals: { + description: 'Where punishment appeals are sent.', + configurable: false + } +}; + +export type GuildLogType = keyof typeof guildLogsObj; +export const guildLogsArr = Object.keys(guildLogsObj).filter( + (s) => guildLogsObj[s as GuildLogType].configurable +) as GuildLogType[]; +type LogChannelDB = { [x in keyof typeof guildLogsObj]?: Snowflake }; + +export type GuildFeatures = keyof typeof guildFeaturesObj; +export const guildFeaturesArr: GuildFeatures[] = Object.keys(guildFeaturesObj).filter( + (f) => !guildFeaturesObj[f as keyof typeof guildFeaturesObj].hidden +) as GuildFeatures[]; diff --git a/lib/models/instance/Highlight.ts b/lib/models/instance/Highlight.ts new file mode 100644 index 0000000..5889fad --- /dev/null +++ b/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/lib/models/instance/Level.ts b/lib/models/instance/Level.ts new file mode 100644 index 0000000..d8d16f0 --- /dev/null +++ b/lib/models/instance/Level.ts @@ -0,0 +1,70 @@ +import { type Snowflake } from 'discord.js'; +import { type Sequelize } from 'sequelize'; +import { BaseModel } from '../BaseModel.js'; +const { DataTypes } = (await import('sequelize')).default; + +export interface LevelModel { + user: Snowflake; + guild: Snowflake; + xp: number; +} + +export interface LevelModelCreationAttributes { + user: Snowflake; + guild: Snowflake; + xp?: number; +} + +/** + * Leveling information for a user in a guild. + */ +export class Level extends BaseModel<LevelModel, LevelModelCreationAttributes> implements LevelModel { + /** + * The user's id. + */ + public declare user: Snowflake; + + /** + * The guild where the user is gaining xp. + */ + public declare guild: Snowflake; + + /** + * The user's xp. + */ + public declare xp: number; + + /** + * The user's level. + */ + public get level(): number { + return Level.convertXpToLevel(this.xp); + } + + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ + public static initModel(sequelize: Sequelize): void { + Level.init( + { + user: { type: DataTypes.STRING, allowNull: false }, + guild: { type: DataTypes.STRING, allowNull: false }, + xp: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0 } + }, + { sequelize } + ); + } + + public static convertXpToLevel(xp: number): number { + return Math.floor((-25 + Math.sqrt(625 + 200 * xp)) / 100); + } + + public static convertLevelToXp(level: number): number { + return 50 * level * level + 25 * level; // 50x² + 25x + } + + public static genRandomizedXp(): number { + return Math.floor(Math.random() * (40 - 15 + 1)) + 15; + } +} diff --git a/lib/models/instance/ModLog.ts b/lib/models/instance/ModLog.ts new file mode 100644 index 0000000..c25f043 --- /dev/null +++ b/lib/models/instance/ModLog.ts @@ -0,0 +1,127 @@ +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 enum ModLogType { + PERM_BAN = 'PERM_BAN', + TEMP_BAN = 'TEMP_BAN', + UNBAN = 'UNBAN', + KICK = 'KICK', + PERM_MUTE = 'PERM_MUTE', + TEMP_MUTE = 'TEMP_MUTE', + UNMUTE = 'UNMUTE', + WARN = 'WARN', + PERM_PUNISHMENT_ROLE = 'PERM_PUNISHMENT_ROLE', + TEMP_PUNISHMENT_ROLE = 'TEMP_PUNISHMENT_ROLE', + REMOVE_PUNISHMENT_ROLE = 'REMOVE_PUNISHMENT_ROLE', + PERM_CHANNEL_BLOCK = 'PERM_CHANNEL_BLOCK', + TEMP_CHANNEL_BLOCK = 'TEMP_CHANNEL_BLOCK', + CHANNEL_UNBLOCK = 'CHANNEL_UNBLOCK', + TIMEOUT = 'TIMEOUT', + REMOVE_TIMEOUT = 'REMOVE_TIMEOUT' +} + +export interface ModLogModel { + id: string; + type: ModLogType; + user: Snowflake; + moderator: Snowflake; + reason: string | null; + duration: number | null; + guild: Snowflake; + evidence: string; + pseudo: boolean; + hidden: boolean; +} + +export interface ModLogModelCreationAttributes { + id?: string; + type: ModLogType; + user: Snowflake; + moderator: Snowflake; + reason?: string | null; + duration?: number; + guild: Snowflake; + evidence?: string; + pseudo?: boolean; + hidden?: boolean; +} + +/** + * A mod log case. + */ +export class ModLog extends BaseModel<ModLogModel, ModLogModelCreationAttributes> implements ModLogModel { + /** + * The primary key of the modlog entry. + */ + public declare id: string; + + /** + * The type of punishment. + */ + public declare type: ModLogType; + + /** + * The user being punished. + */ + public declare user: Snowflake; + + /** + * The user carrying out the punishment. + */ + public declare moderator: Snowflake; + + /** + * The reason the user is getting punished. + */ + public declare reason: string | null; + + /** + * The amount of time the user is getting punished for. + */ + public declare duration: number | null; + + /** + * The guild the user is getting punished in. + */ + public declare guild: Snowflake; + + /** + * Evidence of what the user is getting punished for. + */ + public declare evidence: string; + + /** + * Not an actual modlog just used so a punishment entry can be made. + */ + public declare pseudo: boolean; + + /** + * Hides from the modlog command unless show hidden is specified. + */ + public declare hidden: boolean; + + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ + public static initModel(sequelize: Sequelize): void { + ModLog.init( + { + id: { type: DataTypes.STRING, primaryKey: true, allowNull: false, defaultValue: nanoid }, + type: { type: DataTypes.STRING, allowNull: false }, //? This is not an enum because of a sequelize issue: https://github.com/sequelize/sequelize/issues/2554 + user: { type: DataTypes.STRING, allowNull: false }, + moderator: { type: DataTypes.STRING, allowNull: false }, + duration: { type: DataTypes.STRING, allowNull: true }, + reason: { type: DataTypes.TEXT, allowNull: true }, + guild: { type: DataTypes.STRING, references: { model: 'Guilds', key: 'id' } }, + evidence: { type: DataTypes.TEXT, allowNull: true }, + pseudo: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false }, + hidden: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false } + }, + { sequelize } + ); + } +} diff --git a/lib/models/instance/Reminder.ts b/lib/models/instance/Reminder.ts new file mode 100644 index 0000000..964ea63 --- /dev/null +++ b/lib/models/instance/Reminder.ts @@ -0,0 +1,84 @@ +import { 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 ReminderModel { + id: string; + user: Snowflake; + messageUrl: string; + content: string; + created: Date; + expires: Date; + notified: boolean; +} + +export interface ReminderModelCreationAttributes { + id?: string; + user: Snowflake; + messageUrl: string; + content: string; + created: Date; + expires: Date; + notified?: boolean; +} + +/** + * Represents a reminder the a user has set. + */ +export class Reminder extends BaseModel<ReminderModel, ReminderModelCreationAttributes> implements ReminderModel { + /** + * The id of the reminder. + */ + public declare id: string; + + /** + * The user that the reminder is for. + */ + public declare user: Snowflake; + + /** + * The url of the message where the reminder was created. + */ + public declare messageUrl: string; + + /** + * The content of the reminder. + */ + public declare content: string; + + /** + * The date the reminder was created. + */ + public declare created: Date; + + /** + * The date when the reminder expires. + */ + public declare expires: Date; + + /** + * Whether the user has been notified about the reminder. + */ + public declare notified: boolean; + + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ + public static initModel(sequelize: Sequelize): void { + Reminder.init( + { + id: { type: DataTypes.STRING, primaryKey: true, defaultValue: nanoid }, + user: { type: DataTypes.STRING, allowNull: false }, + messageUrl: { type: DataTypes.STRING, allowNull: false }, + content: { type: DataTypes.TEXT, allowNull: false }, + created: { type: DataTypes.DATE, allowNull: false }, + expires: { type: DataTypes.DATE, allowNull: false }, + notified: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false } + }, + { sequelize } + ); + } +} diff --git a/lib/models/instance/StickyRole.ts b/lib/models/instance/StickyRole.ts new file mode 100644 index 0000000..00e98ce --- /dev/null +++ b/lib/models/instance/StickyRole.ts @@ -0,0 +1,58 @@ +import { type Snowflake } from 'discord.js'; +import { type Sequelize } from 'sequelize'; +import { BaseModel } from '../BaseModel.js'; +const { DataTypes } = (await import('sequelize')).default; + +export interface StickyRoleModel { + user: Snowflake; + guild: Snowflake; + roles: Snowflake[]; + nickname: string; +} +export interface StickyRoleModelCreationAttributes { + user: Snowflake; + guild: Snowflake; + roles: Snowflake[]; + nickname?: string; +} + +/** + * Information about a user's roles and nickname when they leave a guild. + */ +export class StickyRole extends BaseModel<StickyRoleModel, StickyRoleModelCreationAttributes> implements StickyRoleModel { + /** + * The id of the user the roles belongs to. + */ + public declare user: Snowflake; + + /** + * The guild where this should happen. + */ + public declare guild: Snowflake; + + /** + * The roles that the user should have returned + */ + public declare roles: Snowflake[]; + + /** + * The user's previous nickname + */ + public declare nickname: string; + + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ + public static initModel(sequelize: Sequelize): void { + StickyRole.init( + { + user: { type: DataTypes.STRING, allowNull: false }, + guild: { type: DataTypes.STRING, allowNull: false }, + roles: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + nickname: { type: DataTypes.STRING, allowNull: true } + }, + { sequelize } + ); + } +} diff --git a/lib/models/shared/Global.ts b/lib/models/shared/Global.ts new file mode 100644 index 0000000..b1aa0cc --- /dev/null +++ b/lib/models/shared/Global.ts @@ -0,0 +1,67 @@ +import { type Snowflake } from 'discord.js'; +import { type Sequelize } from 'sequelize'; +import { BaseModel } from '../BaseModel.js'; +const { DataTypes } = (await import('sequelize')).default; + +export interface GlobalModel { + environment: 'production' | 'development' | 'beta'; + disabledCommands: string[]; + blacklistedUsers: Snowflake[]; + blacklistedGuilds: Snowflake[]; + blacklistedChannels: Snowflake[]; +} + +export interface GlobalModelCreationAttributes { + environment: 'production' | 'development' | 'beta'; + disabledCommands?: string[]; + blacklistedUsers?: Snowflake[]; + blacklistedGuilds?: Snowflake[]; + blacklistedChannels?: Snowflake[]; +} + +/** + * Data specific to a certain instance of the bot. + */ +export class Global extends BaseModel<GlobalModel, GlobalModelCreationAttributes> implements GlobalModel { + /** + * The bot's environment. + */ + public declare environment: 'production' | 'development' | 'beta'; + + /** + * Globally disabled commands. + */ + public declare disabledCommands: string[]; + + /** + * Globally blacklisted users. + */ + public declare blacklistedUsers: Snowflake[]; + + /** + * Guilds blacklisted from using the bot. + */ + public declare blacklistedGuilds: Snowflake[]; + + /** + * Channels where the bot is prevented from running commands in. + */ + public declare blacklistedChannels: Snowflake[]; + + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ + public static initModel(sequelize: Sequelize): void { + Global.init( + { + environment: { type: DataTypes.STRING, primaryKey: true }, + disabledCommands: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + blacklistedUsers: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + blacklistedGuilds: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + blacklistedChannels: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] } + }, + { sequelize } + ); + } +} diff --git a/lib/models/shared/GuildCount.ts b/lib/models/shared/GuildCount.ts new file mode 100644 index 0000000..7afef56 --- /dev/null +++ b/lib/models/shared/GuildCount.ts @@ -0,0 +1,38 @@ +import { DataTypes, Model, type Sequelize } from 'sequelize'; +import { Environment } from '../../../config/Config.js'; + +export interface GuildCountModel { + timestamp: Date; + environment: Environment; + guildCount: number; +} + +export interface GuildCountCreationAttributes { + timestamp?: Date; + environment: Environment; + guildCount: number; +} + +/** + * The number of guilds that the bot is in for each environment. + */ +export class GuildCount extends Model<GuildCountModel, GuildCountCreationAttributes> implements GuildCountModel { + public declare timestamp: Date; + public declare environment: Environment; + public declare guildCount: number; + + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ + public static initModel(sequelize: Sequelize): void { + GuildCount.init( + { + timestamp: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW }, + environment: { type: DataTypes.STRING, allowNull: false }, + guildCount: { type: DataTypes.BIGINT, allowNull: false } + }, + { sequelize, timestamps: false } + ); + } +} diff --git a/lib/models/shared/MemberCount.ts b/lib/models/shared/MemberCount.ts new file mode 100644 index 0000000..200a58e --- /dev/null +++ b/lib/models/shared/MemberCount.ts @@ -0,0 +1,37 @@ +import { DataTypes, Model, type Sequelize } from 'sequelize'; + +export interface MemberCountModel { + timestamp: Date; + guildId: string; + memberCount: number; +} + +export interface MemberCountCreationAttributes { + timestamp?: Date; + guildId: string; + memberCount: number; +} + +/** + * The member count of each guild that the bot is in that have over 100 members. + */ +export class MemberCount extends Model<MemberCountModel, MemberCountCreationAttributes> implements MemberCountModel { + public declare timestamp: Date; + public declare guildId: string; + public declare memberCount: number; + + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ + public static initModel(sequelize: Sequelize): void { + MemberCount.init( + { + timestamp: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW }, + guildId: { type: DataTypes.STRING, allowNull: false }, + memberCount: { type: DataTypes.BIGINT, allowNull: false } + }, + { sequelize, timestamps: false } + ); + } +} diff --git a/lib/models/shared/Shared.ts b/lib/models/shared/Shared.ts new file mode 100644 index 0000000..dec77d1 --- /dev/null +++ b/lib/models/shared/Shared.ts @@ -0,0 +1,84 @@ +import { Snowflake } from 'discord.js'; +import type { Sequelize } from 'sequelize'; +import { BadWords } from '../../automod/AutomodShared.js'; +import { BaseModel } from '../BaseModel.js'; +const { DataTypes } = (await import('sequelize')).default; + +export interface SharedModel { + primaryKey: 0; + superUsers: Snowflake[]; + privilegedUsers: Snowflake[]; + badLinksSecret: string[]; + badLinks: string[]; + badWords: BadWords; + autoBanCode: string | null; +} + +export interface SharedModelCreationAttributes { + primaryKey?: 0; + superUsers?: Snowflake[]; + privilegedUsers?: Snowflake[]; + badLinksSecret?: string[]; + badLinks?: string[]; + badWords?: BadWords; + autoBanCode?: string; +} + +/** + * Data shared between all bot instances. + */ +export class Shared extends BaseModel<SharedModel, SharedModelCreationAttributes> implements SharedModel { + /** + * The primary key of the shared model. + */ + public declare primaryKey: 0; + + /** + * Trusted users. + */ + public declare superUsers: Snowflake[]; + + /** + * Users that have all permissions that devs have except eval. + */ + public declare privilegedUsers: Snowflake[]; + + /** + * Non-public bad links. + */ + public declare badLinksSecret: string[]; + + /** + * Public Bad links. + */ + public declare badLinks: string[]; + + /** + * Bad words. + */ + public declare badWords: BadWords; + + /** + * Code that is used to match for auto banning users in moulberry's bush + */ + public declare autoBanCode: string; + + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ + public static initModel(sequelize: Sequelize): void { + Shared.init( + { + primaryKey: { type: DataTypes.INTEGER, primaryKey: true, validate: { min: 0, max: 0 } }, + superUsers: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + privilegedUsers: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + badLinksSecret: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + badLinks: { type: DataTypes.JSONB, allowNull: false, defaultValue: [] }, + badWords: { type: DataTypes.JSONB, allowNull: false, defaultValue: {} }, + autoBanCode: { type: DataTypes.TEXT } + }, + { sequelize, freezeTableName: true } + ); + } +} diff --git a/lib/models/shared/Stat.ts b/lib/models/shared/Stat.ts new file mode 100644 index 0000000..8e2e0b3 --- /dev/null +++ b/lib/models/shared/Stat.ts @@ -0,0 +1,72 @@ +import { type Sequelize } from 'sequelize'; +import { BaseModel } from '../BaseModel.js'; +const { DataTypes } = (await import('sequelize')).default; + +type Environment = 'production' | 'development' | 'beta'; + +export interface StatModel { + environment: Environment; + commandsUsed: bigint; + slashCommandsUsed: bigint; +} + +export interface StatModelCreationAttributes { + environment: Environment; + commandsUsed?: bigint; + slashCommandsUsed?: bigint; +} + +/** + * Statistics for each instance of the bot. + */ +export class Stat extends BaseModel<StatModel, StatModelCreationAttributes> implements StatModel { + /** + * The bot's environment. + */ + public declare environment: Environment; + + /** + * The number of commands used + */ + public declare commandsUsed: bigint; + + /** + * The number of slash commands used + */ + public declare slashCommandsUsed: bigint; + + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ + public static initModel(sequelize: Sequelize): void { + Stat.init( + { + environment: { type: DataTypes.STRING, primaryKey: true }, + commandsUsed: { + type: DataTypes.TEXT, + get: function (): bigint { + return BigInt(this.getDataValue('commandsUsed')); + }, + set: function (val: bigint) { + return this.setDataValue('commandsUsed', <any>`${val}`); + }, + allowNull: false, + defaultValue: `${0n}` + }, + slashCommandsUsed: { + type: DataTypes.TEXT, + get: function (): bigint { + return BigInt(this.getDataValue('slashCommandsUsed')); + }, + set: function (val: bigint) { + return this.setDataValue('slashCommandsUsed', <any>`${val}`); + }, + allowNull: false, + defaultValue: `${0n}` + } + }, + { sequelize } + ); + } +} |