aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.vscode/launch.json6
-rw-r--r--src/bot.ts1
-rw-r--r--src/commands/dev/superUser.ts6
-rw-r--r--src/commands/info/userInfo.ts2
-rw-r--r--src/lib/extensions/discord-akairo/BushClient.ts76
-rw-r--r--src/lib/extensions/discord-akairo/BushClientUtil.ts42
-rw-r--r--src/lib/index.ts1
-rw-r--r--src/lib/models/Global.ts9
-rw-r--r--src/lib/models/Guild.ts1
-rw-r--r--src/lib/models/Level.ts1
-rw-r--r--src/lib/models/Shared.ts49
-rw-r--r--src/lib/models/__helpers.ts1
-rw-r--r--src/lib/utils/BushCache.ts7
-rw-r--r--src/tasks/updateCache.ts28
-rw-r--r--src/tasks/updateSuperUsers.ts28
15 files changed, 184 insertions, 74 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 3237d6d..91f1fa1 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,6 +1,12 @@
{
"configurations": [
{
+ "command": "yarn start",
+ "name": "yarn start",
+ "request": "launch",
+ "type": "node-terminal"
+ },
+ {
"command": "yarn dev",
"name": "BushBot",
"request": "launch",
diff --git a/src/bot.ts b/src/bot.ts
index 9120f77..56a6148 100644
--- a/src/bot.ts
+++ b/src/bot.ts
@@ -9,6 +9,7 @@ const isDry = process.argv.includes('dry');
if (!isDry) new Sentry(dirname(fileURLToPath(import.meta.url)) || process.cwd());
BushClient.extendStructures();
const client = new BushClient(config);
+if (!isDry) await client.dbPreInit();
await client.init();
if (isDry) {
await client.destroy();
diff --git a/src/commands/dev/superUser.ts b/src/commands/dev/superUser.ts
index 3a6406d..f937ad4 100644
--- a/src/commands/dev/superUser.ts
+++ b/src/commands/dev/superUser.ts
@@ -1,4 +1,4 @@
-import { BushCommand, Global, type ArgType, type BushMessage } from '#lib';
+import { BushCommand, type ArgType, type BushMessage } from '#lib';
import { type ArgumentOptions, type Flag } from 'discord-akairo';
export default class SuperUserCommand extends BushCommand {
@@ -57,12 +57,12 @@ export default class SuperUserCommand extends BushCommand {
if (!message.author.isOwner())
return await message.util.reply(`${util.emojis.error} Only my developers can run this command.`);
- const superUsers: string[] = (await Global.findByPk(client.config.environment))?.superUsers ?? [];
+ const superUsers: string[] = util.getShared('superUsers');
if (action === 'add' ? superUsers.includes(user.id) : !superUsers.includes(user.id))
return message.util.reply(`${util.emojis.warn} \`${user.tag}\` is ${action === 'add' ? 'already' : 'not'} a superuser.`);
- const success = await util.insertOrRemoveFromGlobal(action, 'superUsers', user.id).catch(() => false);
+ const success = await util.insertOrRemoveFromShared(action, 'superUsers', user.id).catch(() => false);
if (success) {
return await message.util.reply(
diff --git a/src/commands/info/userInfo.ts b/src/commands/info/userInfo.ts
index f8c7c68..609bd94 100644
--- a/src/commands/info/userInfo.ts
+++ b/src/commands/info/userInfo.ts
@@ -54,7 +54,7 @@ export default class UserInfoCommand extends BushCommand {
public static async makeUserInfoEmbed(user: BushUser, member?: BushGuildMember, guild?: BushGuild | null) {
const emojis = [];
- const superUsers = client.cache.global.superUsers;
+ const superUsers = util.getShared('superUsers');
const userEmbed: MessageEmbed = new MessageEmbed()
.setTitle(util.discord.escapeMarkdown(user.tag))
diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts
index 8cbc6fe..65b60eb 100644
--- a/src/lib/extensions/discord-akairo/BushClient.ts
+++ b/src/lib/extensions/discord-akairo/BushClient.ts
@@ -42,7 +42,7 @@ import {
import EventEmitter from 'events';
import path from 'path';
import readline from 'readline';
-import type { Sequelize as SequelizeType } from 'sequelize';
+import type { Options as SequelizeOptions, Sequelize as SequelizeType } from 'sequelize';
import { fileURLToPath } from 'url';
import UpdateCacheTask from '../../../tasks/updateCache.js';
import UpdateStatsTask from '../../../tasks/updateStats.js';
@@ -52,6 +52,7 @@ import { Guild as GuildModel } from '../../models/Guild.js';
import { Level } from '../../models/Level.js';
import { ModLog } from '../../models/ModLog.js';
import { Reminder } from '../../models/Reminder.js';
+import { Shared } from '../../models/Shared.js';
import { Stat } from '../../models/Stat.js';
import { StickyRole } from '../../models/StickyRole.js';
import { AllowedMentions } from '../../utils/AllowedMentions.js';
@@ -152,9 +153,14 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
public contextMenuCommandHandler: ContextMenuCommandHandler;
/**
- * The database connection for the bot.
+ * The database connection for this instance of the bot (production, beta, or development).
*/
- public db: SequelizeType;
+ public instanceDB: SequelizeType;
+
+ /**
+ * The database connection that is shared between all instances of the bot.
+ */
+ public sharedDB: SequelizeType;
/**
* A custom logging system for the bot.
@@ -201,6 +207,9 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
this.token = config.token as If<Ready, string, string | null>;
this.config = config;
+ this.util = new BushClientUtil(this);
+
+ /* handlers */
this.listenerHandler = new BushListenerHandler(this, {
directory: path.join(__dirname, '..', '..', '..', 'listeners'),
automateCategories: true
@@ -250,9 +259,9 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
directory: path.join(__dirname, '..', '..', '..', 'context-menu-commands'),
automateCategories: true
});
- this.util = new BushClientUtil(this);
- this.db = new Sequelize({
- database: this.config.isDevelopment ? 'bushbot-dev' : this.config.isBeta ? 'bushbot-beta' : 'bushbot',
+
+ /* databases */
+ const sharedDBOptions: SequelizeOptions = {
username: this.config.db.username,
password: this.config.db.password,
dialect: 'postgres',
@@ -260,9 +269,18 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
port: this.config.db.port,
logging: this.config.logging.db ? (sql) => this.logger.debug(sql) : false,
timezone: 'America/New_York'
+ };
+ this.instanceDB = new Sequelize({
+ ...sharedDBOptions,
+ database: this.config.isDevelopment ? 'bushbot-dev' : this.config.isBeta ? 'bushbot-beta' : 'bushbot'
+ });
+ this.sharedDB = new Sequelize({
+ ...sharedDBOptions,
+ database: 'bushbot-shared'
});
- // global objects
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ /* global objects */
global.client = this;
global.util = this.util;
}
@@ -321,7 +339,7 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
this.commandHandler.useTaskHandler(this.taskHandler);
this.commandHandler.useContextMenuCommandHandler(this.contextMenuCommandHandler);
this.commandHandler.ignorePermissions = this.config.owners;
- this.commandHandler.ignoreCooldown = [...new Set([...this.config.owners, ...this.cache.global.superUsers])];
+ this.commandHandler.ignoreCooldown = [...new Set([...this.config.owners, ...this.cache.shared.superUsers])];
this.listenerHandler.setEmitters({
client: this,
commandHandler: this.commandHandler,
@@ -377,23 +395,36 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
/**
* Connects to the database, initializes models, and creates tables if they do not exist.
*/
- private async dbPreInit() {
+ async dbPreInit() {
+ try {
+ await this.instanceDB.authenticate();
+ GuildModel.initModel(this.instanceDB, this);
+ ModLog.initModel(this.instanceDB);
+ ActivePunishment.initModel(this.instanceDB);
+ Level.initModel(this.instanceDB);
+ StickyRole.initModel(this.instanceDB);
+ Reminder.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) {
+ await this.console.error(
+ 'startup',
+ `Failed to connect to <<instance database>> with error:\n${util.inspect(e, { colors: true, depth: 1 })}`,
+ false
+ );
+ process.exit(2);
+ }
try {
- await this.db.authenticate();
- Global.initModel(this.db);
- GuildModel.initModel(this.db, this);
- ModLog.initModel(this.db);
- ActivePunishment.initModel(this.db);
- Level.initModel(this.db);
- StickyRole.initModel(this.db);
- Stat.initModel(this.db);
- Reminder.initModel(this.db);
- await this.db.sync({ alter: true }); // Sync all tables to fix everything if updated
- await this.console.success('startup', `Successfully connected to <<database>>.`, false);
+ await this.sharedDB.authenticate();
+ Stat.initModel(this.sharedDB);
+ Global.initModel(this.sharedDB);
+ Shared.initModel(this.sharedDB);
+ await this.sharedDB.sync({ alter: true }); // Sync all tables to fix everything if updated
+ await this.console.success('startup', `Successfully connected to <<shared database>>.`, false);
} catch (e) {
await this.console.error(
'startup',
- `Failed to connect to <<database>> with error:\n${util.inspect(e, { colors: true, depth: 1 })}`,
+ `Failed to connect to <<shared database>> with error:\n${util.inspect(e, { colors: true, depth: 1 })}`,
false
);
process.exit(2);
@@ -416,7 +447,6 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
});
try {
- await this.dbPreInit();
await UpdateCacheTask.init(this);
void this.console.success('startup', `Successfully created <<cache>>.`, false);
this.stats.commandsUsed = await UpdateStatsTask.init();
@@ -443,7 +473,7 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
public override isSuperUser(user: BushUserResolvable): boolean {
const userID = this.users.resolveId(user)!;
- return !!client.cache?.global?.superUsers?.includes(userID) || this.config.owners.includes(userID);
+ return !!client.cache.shared.superUsers.includes(userID) || this.config.owners.includes(userID);
}
}
diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts
index 9a8e408..cdb883d 100644
--- a/src/lib/extensions/discord-akairo/BushClientUtil.ts
+++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts
@@ -2,6 +2,8 @@ import {
Arg,
BushConstants,
Global,
+ Shared,
+ SharedCache,
type BushClient,
type BushInspectOptions,
type BushMessage,
@@ -442,6 +444,12 @@ export class BushClientUtil extends ClientUtil {
return key ? client.cache.global[key] : client.cache.global;
}
+ public getShared(): SharedCache;
+ public getShared<K extends keyof SharedCache>(key: K): SharedCache[K];
+ public getShared(key?: keyof SharedCache) {
+ return key ? client.cache.shared[key] : client.cache.shared;
+ }
+
/**
* Add or remove an element from an array stored in the Globals database.
* @param action Either `add` or `remove` an element.
@@ -463,6 +471,25 @@ export class BushClientUtil extends ClientUtil {
}
/**
+ * Add or remove an element from an array stored in the Shared database.
+ * @param action Either `add` or `remove` an element.
+ * @param key The key of the element in the shared cache to update.
+ * @param value The value to add/remove from the array.
+ */
+ public async insertOrRemoveFromShared<K extends keyof typeof client['cache']['shared']>(
+ action: 'add' | 'remove',
+ key: K,
+ value: typeof client['cache']['shared'][K][0]
+ ): Promise<Shared | void> {
+ const row = (await Shared.findByPk(0)) ?? (await Shared.create());
+ const oldValue: any[] = row[key];
+ const newValue = this.addOrRemoveFromArray(action, oldValue, value);
+ row[key] = newValue;
+ client.cache.shared[key] = newValue;
+ return await row.save().catch((e) => this.handleError('insertOrRemoveFromShared', e));
+ }
+
+ /**
* Updates an element in the Globals database.
* @param key The key in the global cache to update.
* @param value The value to set the key to.
@@ -479,6 +506,21 @@ export class BushClientUtil extends ClientUtil {
}
/**
+ * Updates an element in the Shared database.
+ * @param key The key in the shared cache to update.
+ * @param value The value to set the key to.
+ */
+ public async setShared<K extends keyof typeof client['cache']['shared']>(
+ key: K,
+ value: typeof client['cache']['shared'][K]
+ ): Promise<Shared | void> {
+ const row = (await Shared.findByPk(0)) ?? (await Shared.create());
+ row[key] = value;
+ client.cache.shared[key] = value;
+ return await row.save().catch((e) => this.handleError('setShared', e));
+ }
+
+ /**
* Add or remove an item from an array. All duplicates will be removed.
* @param action Either `add` or `remove` an element.
* @param array The array to add/remove an element from.
diff --git a/src/lib/index.ts b/src/lib/index.ts
index 8809e27..37bc443 100644
--- a/src/lib/index.ts
+++ b/src/lib/index.ts
@@ -73,6 +73,7 @@ export * from './models/Guild.js';
export * from './models/Level.js';
export * from './models/ModLog.js';
export * from './models/Reminder.js';
+export * from './models/Shared.js';
export * from './models/Stat.js';
export * from './models/StickyRole.js';
export * from './utils/AllowedMentions.js';
diff --git a/src/lib/models/Global.ts b/src/lib/models/Global.ts
index 30a9d38..1deb090 100644
--- a/src/lib/models/Global.ts
+++ b/src/lib/models/Global.ts
@@ -2,12 +2,10 @@ import { type Snowflake } from 'discord.js';
import { type Sequelize } from 'sequelize';
import { BaseModel } from './BaseModel.js';
import { jsonArray } from './__helpers.js';
-
const { DataTypes } = (await import('sequelize')).default;
export interface GlobalModel {
environment: 'production' | 'development' | 'beta';
- superUsers: Snowflake[];
disabledCommands: string[];
blacklistedUsers: Snowflake[];
blacklistedGuilds: Snowflake[];
@@ -16,7 +14,6 @@ export interface GlobalModel {
export interface GlobalModelCreationAttributes {
environment: 'production' | 'development' | 'beta';
- superUsers?: Snowflake[];
disabledCommands?: string[];
blacklistedUsers?: Snowflake[];
blacklistedGuilds?: Snowflake[];
@@ -30,11 +27,6 @@ export class Global extends BaseModel<GlobalModel, GlobalModelCreationAttributes
public declare environment: 'production' | 'development' | 'beta';
/**
- * Trusted users.
- */
- public declare superUsers: Snowflake[];
-
- /**
* Globally disabled commands.
*/
public declare disabledCommands: string[];
@@ -62,7 +54,6 @@ export class Global extends BaseModel<GlobalModel, GlobalModelCreationAttributes
Global.init(
{
environment: { type: DataTypes.STRING, primaryKey: true },
- superUsers: jsonArray('superUsers'),
disabledCommands: jsonArray('disabledCommands'),
blacklistedUsers: jsonArray('blacklistedUsers'),
blacklistedGuilds: jsonArray('blacklistedGuilds'),
diff --git a/src/lib/models/Guild.ts b/src/lib/models/Guild.ts
index dfee37b..80bd119 100644
--- a/src/lib/models/Guild.ts
+++ b/src/lib/models/Guild.ts
@@ -5,7 +5,6 @@ import { type BadWords } from '../common/AutoMod.js';
import { type BushClient } from '../extensions/discord-akairo/BushClient.js';
import { BaseModel } from './BaseModel.js';
import { jsonArray, jsonObject } from './__helpers.js';
-
const { DataTypes } = (await import('sequelize')).default;
export interface GuildModel {
diff --git a/src/lib/models/Level.ts b/src/lib/models/Level.ts
index 2ed787d..e247779 100644
--- a/src/lib/models/Level.ts
+++ b/src/lib/models/Level.ts
@@ -1,7 +1,6 @@
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 {
diff --git a/src/lib/models/Shared.ts b/src/lib/models/Shared.ts
new file mode 100644
index 0000000..dd7682b
--- /dev/null
+++ b/src/lib/models/Shared.ts
@@ -0,0 +1,49 @@
+import { type Sequelize } from 'sequelize';
+import { BaseModel } from './BaseModel.js';
+import { jsonArray } from './__helpers.js';
+const { DataTypes } = (await import('sequelize')).default;
+
+export interface SharedModel {
+ primaryKey: 0;
+ superUsers: string[];
+ badLinks: string[];
+}
+
+export interface SharedModelCreationAttributes {
+ primaryKey?: 0;
+ superUsers?: string[];
+ badLinks?: string[];
+}
+
+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: string[];
+
+ //todo
+ /**
+ * Bad links.
+ */
+ public declare badLinks: 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: jsonArray('superUsers'),
+ badLinks: jsonArray('badLinks')
+ },
+ { sequelize, freezeTableName: true }
+ );
+ }
+}
diff --git a/src/lib/models/__helpers.ts b/src/lib/models/__helpers.ts
index 049dc00..bbfe328 100644
--- a/src/lib/models/__helpers.ts
+++ b/src/lib/models/__helpers.ts
@@ -1,5 +1,4 @@
import { type Model } from 'sequelize';
-
const { DataTypes } = (await import('sequelize')).default;
export function jsonParseGet(this: Model, key: string): any {
diff --git a/src/lib/utils/BushCache.ts b/src/lib/utils/BushCache.ts
index cea0aea..5654495 100644
--- a/src/lib/utils/BushCache.ts
+++ b/src/lib/utils/BushCache.ts
@@ -3,15 +3,20 @@ import { Collection, type Snowflake } from 'discord.js';
export class BushCache {
public global = new GlobalCache();
+ public shared = new SharedCache();
public guilds = new GuildCache();
}
export class GlobalCache {
- public superUsers: Snowflake[] = [];
public disabledCommands: string[] = [];
public blacklistedChannels: Snowflake[] = [];
public blacklistedGuilds: Snowflake[] = [];
public blacklistedUsers: Snowflake[] = [];
}
+export class SharedCache {
+ public superUsers: Snowflake[] = [];
+ public badLinks: string[] = [];
+}
+
export class GuildCache extends Collection<Snowflake, Guild> {}
diff --git a/src/tasks/updateCache.ts b/src/tasks/updateCache.ts
index 8f9cc5d..9084c1c 100644
--- a/src/tasks/updateCache.ts
+++ b/src/tasks/updateCache.ts
@@ -1,4 +1,4 @@
-import { Global, Guild, type BushClient } from '#lib';
+import { Global, Guild, Shared, type BushClient } from '#lib';
import { BushTask } from '../lib/extensions/discord-akairo/BushTask.js';
import config from './../config/options.js';
@@ -11,23 +11,39 @@ export default class UpdateCacheTask extends BushTask {
}
public override async exec() {
- await UpdateCacheTask.updateGlobalCache(client);
- await UpdateCacheTask.#updateGuildCache(client);
+ await Promise.all([
+ UpdateCacheTask.#updateGlobalCache(client),
+ UpdateCacheTask.#updateSharedCache(client),
+ UpdateCacheTask.#updateGuildCache(client)
+ ]);
void client.logger.verbose(`UpdateCache`, `Updated cache.`);
}
public static async init(client: BushClient) {
- await UpdateCacheTask.updateGlobalCache(client);
- await UpdateCacheTask.#updateGuildCache(client);
+ await Promise.all([
+ UpdateCacheTask.#updateGlobalCache(client),
+ UpdateCacheTask.#updateSharedCache(client),
+ UpdateCacheTask.#updateGuildCache(client)
+ ]);
}
- private static async updateGlobalCache(client: BushClient) {
+ static async #updateGlobalCache(client: BushClient) {
const environment = config.environment;
const row: { [x: string]: any } = ((await Global.findByPk(environment)) ?? (await Global.create({ environment }))).toJSON();
for (const option in row) {
if (Object.keys(client.cache.global).includes(option)) {
client.cache.global[option as keyof typeof client.cache.global] = row[option];
+ }
+ }
+ }
+
+ static async #updateSharedCache(client: BushClient) {
+ const row: { [x: string]: any } = ((await Shared.findByPk(0)) ?? (await Shared.create())).toJSON();
+
+ for (const option in row) {
+ if (Object.keys(client.cache.shared).includes(option)) {
+ client.cache.shared[option as keyof typeof client.cache.shared] = row[option];
if (option === 'superUsers') client.superUserID = row[option];
}
}
diff --git a/src/tasks/updateSuperUsers.ts b/src/tasks/updateSuperUsers.ts
deleted file mode 100644
index 8dcd00e..0000000
--- a/src/tasks/updateSuperUsers.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { BushTask, Global } from '#lib';
-
-export default class UpdateSuperUsersTask extends BushTask {
- public constructor() {
- super('updateSuperUsers', {
- delay: 10_000, // 10 seconds
- runOnStart: true
- });
- }
-
- public override async exec() {
- const superUsers = client.guilds.cache
- .get(client.config.supportGuild.id)
- ?.members.cache.filter(
- (member) => (member.roles.cache.has('865954009280938056') || member.permissions.has('ADMINISTRATOR')) && !member.user.bot
- )
- .map((member) => member.id);
-
- const row =
- (await Global.findByPk(client.config.environment)) ?? (await Global.create({ environment: client.config.environment }));
-
- row.superUsers = superUsers ?? row.superUsers;
- client.cache.global.superUsers = superUsers ?? row.superUsers;
- await row.save();
-
- void client.logger.verbose(`updateSuperUsers`, 'Updated superusers.');
- }
-}