Skip to content

Commit

Permalink
Move guild settings to database
Browse files Browse the repository at this point in the history
Adapt feature checker to database
Adapt codebase to database
  • Loading branch information
TrojanerHD committed Jan 14, 2023
1 parent 44080ba commit 48cd86b
Show file tree
Hide file tree
Showing 20 changed files with 986 additions and 110 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
/node_modules/
yarn-error.log
/build/
settings.json
settings.json
settings.db
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"dependencies": {
"discord.js": "^13.6.0",
"dotenv": "^16.0.0",
"express": "^4.18.1"
"express": "^4.18.1",
"sqlite3": "^5.1.2"
}
}
43 changes: 29 additions & 14 deletions src/DiscordClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ThreadMember,
MessageOptions,
TextBasedChannel,
Guild,
} from 'discord.js';
import MessageHandler from './messages/MessageHandler';
import ReactionHandler from './ReactionHandler';
Expand All @@ -17,12 +18,13 @@ import RoleChannelManager from './roles/RoleChannelManager';
import Settings from './Settings';
import DMManager from './twitch/DMManager';
import FeatureChecker from './FeatureChecker';
import GuildSettings from './settings/GuildSettings';

/**
* The discord client handler and initializer of the bot
*/
export default class DiscordClient {
static _client: Client = new Client({
public static _client: Client = new Client({
intents: [
Intents.FLAGS.GUILDS,
Intents.FLAGS.GUILD_MESSAGES,
Expand All @@ -31,44 +33,57 @@ export default class DiscordClient {
],
});

public static _safeGuilds: Guild[] = [];

constructor() {
new FeatureChecker();
new MessageHandler();
new ReactionHandler();
DiscordClient._client.on('ready', this.onReady.bind(this));
DiscordClient._client.on('threadCreate', this.onThreadCreate);
DiscordClient._client.on(
'guildCreate',
(guild: Guild): Promise<void> => new FeatureChecker().checkGuild(guild)
);
}

/**
* Logs the discord client into the discord api and starts the handlers
*/
login(): void {
DiscordClient._client
.login(process.env.DISCORD_TOKEN)
.catch(console.error)
.then((): void => {
if (Settings.settings['twitch-id'] !== '' && process.env.TWITCH_TOKEN)
this.startTwitch();
});
DiscordClient._client.login(process.env.DISCORD_TOKEN).catch(console.error);
}

/**
* Fires when the Discord bot is ready
*/
private async onReady(): Promise<void> {
DiscordClient._safeGuilds = DiscordClient._client.guilds.cache.toJSON();
for (const guild of DiscordClient._safeGuilds)
if (!GuildSettings.settings(guild.id))
GuildSettings.saveSettings(guild, {
permissionRoles: [],
roles: [],
streamers: [],
});
await new FeatureChecker().check();
new MessageHandler();
new ReactionHandler();
if (Settings.settings['twitch-id'] !== '' && process.env.TWITCH_TOKEN)
this.startTwitch();
this.joinAllThreads();
new TalkingChannel();
if (!DiscordClient._client.application?.owner)
await DiscordClient._client.application?.fetch().catch(console.error);
MessageHandler.addCommands();
if (Settings.settings.roles.length !== 0) new RoleChannelManager();
for (const guild of DiscordClient._safeGuilds) {
if ((await GuildSettings.settings(guild.id)).roles.length !== 0)
new RoleChannelManager(guild).run();
}
}

/**
* Joins all threads to be available there
*/
private joinAllThreads(): void {
for (const guild of DiscordClient._client.guilds.cache.toJSON())
for (const guild of DiscordClient._safeGuilds)
for (const threadChannel of (
guild.channels.cache.filter(
(channel: GuildChannel | ThreadChannel): boolean =>
Expand All @@ -86,7 +101,7 @@ export default class DiscordClient {
* Starts doing stuff with Twitch for the #live channel and DM notification handling
*/
private startTwitch(): void {
new LiveChannel();
for (const guild of DiscordClient._safeGuilds) new LiveChannel(guild.id);
new DMManager();
}

Expand Down
53 changes: 42 additions & 11 deletions src/FeatureChecker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Guild } from 'discord.js';
import DiscordClient from './DiscordClient';
import Settings from './Settings';
import GuildSettings from './settings/GuildSettings';
import { GuildInfo } from './settings/SettingsDB';

/**
* Checks what features are enabled in the settings
Expand All @@ -14,22 +18,17 @@ export default class FeatureChecker {
*/
#crash: boolean = false;

constructor() {
public async check() {
if (Settings.settings.logging === 'verbose')
this.status('Logging set to verbose');
if (Settings.settings['permission-roles'].length === 0)
this.warning('No permitted roles set (field "permission-roles" empty)');
if (Settings.settings.roles.length === 0) this.status('Roles disabled');
else this.status('Roles enabled');
for (const guild of DiscordClient._client.guilds.cache.toJSON())
await this.checkGuild(guild);

if (Settings.settings['twitch-id'])
if (process.env.TWITCH_TOKEN) this.status('Twitch enabled');
else this.warning('No Twitch token provided');
else if (process.env.TWITCH_TOKEN) this.warning('No Twitch ID provided');
else this.status('Twitch disabled');
if (Settings.settings.roles.length > 25)
this.error(
'Currently, only a maximum of 25 roles is allowed. If you need more, file an issue at https://github.com/TrojanerHD/TrojanerBot/issues/new'
);
if (
process.argv[
process.argv.findIndex((value: string): boolean => value === '-r') + 1
Expand All @@ -42,6 +41,38 @@ export default class FeatureChecker {
if (this.#crash) process.exit(1);
}

public async checkGuild(guild: Guild): Promise<void> {
const guildInfo: string = `for guild ${guild.id} (${guild.name})`;
let error: boolean = false;
try {
const info: GuildInfo = await GuildSettings.settings(guild.id);
if (info.permissionRoles.length === 0)
this.warning(`No permitted roles set for ${guildInfo}`);
if (info.roles.length === 0) this.status(`Roles disabled ${guildInfo}`);
else this.status(`Roles enabled ${guildInfo}`);
if (info.roles.length > 25) {
error = true;
this.error(
`Currently, only a maximum of 25 roles is allowed. Guild ${guild.id} (${guild.name}) has more than 25 roles. If you need more, file an issue at https://github.com/TrojanerHD/TrojanerBot/issues/new`,
false
);
}
} catch (e: unknown) {
error = true;
this.error(
`Could not load information ${guildInfo} with reason ${e}`,
false
);
} finally {
if (error) {
DiscordClient._safeGuilds = DiscordClient._safeGuilds.filter(
(otherGuild: Guild) => otherGuild.id !== guild.id
);
this.warning(`Limited features due to problems ${guildInfo}`);
}
}
}

/**
* Appends a warning to the log output
* @param message The warning message to append
Expand All @@ -64,8 +95,8 @@ export default class FeatureChecker {
* Appends an error to the log output and will crash the bot
* @param message The error message to append
*/
private error(message: string): void {
private error(message: string, crash: boolean = true): void {
this.#message += `Error: ${message}\n`;
this.#crash = true;
if (!this.#crash) this.#crash = crash;
}
}
33 changes: 22 additions & 11 deletions src/ReactionHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
import DiscordClient from './DiscordClient';
import assignRoles from './roles/assignRoles';
import MessageHandler from './messages/MessageHandler';
import Settings, { RolesField } from './Settings';
import { RolesField } from './settings/SettingsDB';
import GuildSettings from './settings/GuildSettings';

/**
* Handles reactions (button presses, slash-commands)
Expand All @@ -26,14 +27,16 @@ export default class ReactionHandler {
* Fires whenever an interaction has been made
* @param interaction The interaction that has happened
*/
private onReaction(interaction: Interaction): void {
private async onReaction(interaction: Interaction): Promise<void> {
// Checks whether the interaction was a button
if (interaction.isButton()) {
// If the id of the button is a role name, the interaction's origin is from the role picker
const settingsRole: RolesField | undefined = Settings.settings.roles.find(
(role: RolesField): boolean =>
role.name.toLowerCase() === interaction.customId
);
const settingsRole: RolesField | undefined = interaction.guildId
? (await GuildSettings.settings(interaction.guildId)).roles.find(
(role: RolesField): boolean =>
role.name.toLowerCase() === interaction.customId
)
: undefined;
if (settingsRole !== undefined) {
// Hack to not reply with anything
interaction.reply({}).catch((reason: any): void => {
Expand Down Expand Up @@ -62,7 +65,8 @@ export default class ReactionHandler {
interaction
.reply({
content: 'Select your roles',
components: this.generateRoleSelectorComponent(
components: await this.generateRoleSelectorComponent(
interaction.guildId,
interaction.member as GuildMember | null
),
ephemeral: true,
Expand All @@ -84,16 +88,22 @@ export default class ReactionHandler {
* @param member The guild member the selector is created for
* @returns A message action row array containing all selectable roles
*/
private generateRoleSelectorComponent(member: GuildMember | null): MessageActionRow[] {
private async generateRoleSelectorComponent(
guild: string | null,
member: GuildMember | null
): Promise<MessageActionRow[]> {
const messageActionRows: MessageActionRow[] = [];
let currentMessageActionRow: MessageActionRow;
if (guild === null) return [];

const roles = (await GuildSettings.settings(guild)).roles;
// Every row can contain up to five roles
for (let i = 0; i < Settings.settings.roles.length / 5; i++) {
for (let i = 0; i < roles.length / 5; i++) {
currentMessageActionRow = new MessageActionRow();
messageActionRows.push(currentMessageActionRow);
// Add the current five roles as component
currentMessageActionRow.addComponents(
Settings.settings.roles.slice(i * 5, i * 5 + 5).map(
roles.slice(i * 5, i * 5 + 5).map(
// Map the roles stored in settings
(role: RolesField): MessageActionRowComponentResolvable => ({
customId: role.name.toLowerCase(),
Expand Down Expand Up @@ -121,7 +131,8 @@ export default class ReactionHandler {
interaction
.editReply({
content: 'Select your roles',
components: this.generateRoleSelectorComponent(
components: await this.generateRoleSelectorComponent(
interaction.guildId,
interaction.member as GuildMember | null
),
})
Expand Down
11 changes: 0 additions & 11 deletions src/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,8 @@ import { Channel } from './messages/StreamerCommand';
import fs from 'fs';
import DMManager from './twitch/DMManager';

export interface RolesField {
name: string;
emoji: string;
description?: string;
}
export interface SettingsJSON {
'twitch-id': string;
'permission-roles': string[];
roles: RolesField[];
streamers: string[];
logging: 'verbose' | 'errors' | 'warnings';
'streamer-subscriptions': Channel[];
'express-port'?: number;
Expand All @@ -34,9 +26,6 @@ export default class Settings {
*/
private static _settings: SettingsJSON = {
'twitch-id': '',
'permission-roles': [],
roles: [],
streamers: [],
logging: 'warnings',
'streamer-subscriptions': [],
};
Expand Down
2 changes: 1 addition & 1 deletion src/TalkingChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default class TalkingChannel {
'voiceStateUpdate',
this.onVoiceStateUpdate.bind(this)
);
for (const guild of DiscordClient._client.guilds.cache.toJSON())
for (const guild of DiscordClient._safeGuilds)
for (const channel of guild.channels.cache.toJSON())
if (channel.name.startsWith('Talking '))
channel.delete().catch(console.error);
Expand Down
2 changes: 1 addition & 1 deletion src/messages/DeployCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default class DeployCommand extends Command {
.then(this.commandsFetched)
.catch(console.error);

for (const guild of DiscordClient._client.guilds.cache.toJSON())
for (const guild of DiscordClient._safeGuilds)
guild.commands
.fetch()
.then(this.commandsFetched)
Expand Down
15 changes: 9 additions & 6 deletions src/messages/MessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import DeployCommand from './DeployCommand';
import LinkResolve from './LinkResolve';
import Command from './Command';
import CommandPermissions from './permissions/CommandPermissions';
import Settings from '../Settings';
import GuildSettings from '../settings/GuildSettings';

export type ApplicationCommandType = ApplicationCommand<{
guild: GuildResolvable;
Expand Down Expand Up @@ -55,12 +55,13 @@ export default class MessageHandler {

DiscordClient._client.application?.commands.set(dmCommands);

for (const guild of DiscordClient._client.guilds.cache.toJSON()) {
for (const guild of DiscordClient._safeGuilds) {
guild.commands
.fetch()
.then((): void => {
const commandPermissions: CommandPermissions =
new CommandPermissions();
const commandPermissions: CommandPermissions = new CommandPermissions(
guild
);
guild.commands
.set(guildCommands)
.then(commandPermissions.onCommandsSet.bind(commandPermissions))
Expand All @@ -75,15 +76,17 @@ export default class MessageHandler {
* Also checks for a message link in the message that could be processed as quote
* @param message The message to be processed
*/
onMessage(message: Message): void {
async onMessage(message: Message): Promise<void> {
if (message.channel.type === 'DM' || message.author.bot) return;
if (message.content.match(/https:\/\/discord(app)?\.(com|gg)\/channels/))
new LinkResolve().handleCommand(message.channel, message);

if (message.content === '!deploy') {
const permissionRoles = (await GuildSettings.settings(message.guildId!))
.permissionRoles;
if (
!message.member!.roles.cache.some((role: Role): boolean =>
Settings.settings['permission-roles'].includes(role.name)
permissionRoles.includes(role.name)
)
) {
message
Expand Down
Loading

0 comments on commit 48cd86b

Please sign in to comment.