Skip to content

Commit

Permalink
feat(bot): added whitelist commands + helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
tabarra committed Jan 12, 2023
1 parent 991a80c commit 6f49bc7
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 47 deletions.
57 changes: 19 additions & 38 deletions core/components/DiscordBot/commands/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { txEnv } from '@core/globalData';
import TxAdmin from '@core/txAdmin';
import { cloneDeep } from 'lodash-es';
import { MessageButtonStyles } from '../extractedEnums';
import { embedder, ensurePermission, logDiscordAdminAction } from '../discordHelpers';
const { dir, log, logOk, logWarn, logError } = logger(modulename);

//Humanizer options
Expand Down Expand Up @@ -194,24 +195,8 @@ export const removeOldEmbed = async (interaction: BaseCommandInteraction, txAdmi

export default async (interaction: CommandInteraction, txAdmin: TxAdmin) => {
//Check permissions
//TODO: generalize this to other commands?
const admin = txAdmin.adminVault.getAdminByProviderUID(interaction.user.id);
if (!admin) {
return await interaction.reply({
content: 'your Discord ID is not registered in txAdmin :face_with_monocle:',
ephemeral: true
});
}
if (
admin.master !== true
&& !admin.permissions.includes('all_permissions')
&& !admin.permissions.includes('settings.write')
) {
return await interaction.reply({
content: 'you do not have the "Settings: Change" permissions required to set the embed :face_with_raised_eyebrow:',
ephemeral: true
});
}
const adminName = await ensurePermission(interaction, txAdmin, 'settings.write');
if (typeof adminName !== 'string') return;

//Attempt to remove old message
const isRemoveOnly = (interaction.options.getSubcommand() === 'remove');
Expand All @@ -220,17 +205,15 @@ export default async (interaction: CommandInteraction, txAdmin: TxAdmin) => {
txAdmin.persistentCache.delete('discord:status:channelId');
txAdmin.persistentCache.delete('discord:status:messageId');
if (isRemoveOnly) {
return await interaction.reply({
content: `Old status embed removed.`,
ephemeral: true
});
const msg = `Old status embed removed.`;
logDiscordAdminAction(txAdmin, adminName, msg);
return await interaction.reply(embedder.success(msg, true));
}
} catch (error) {
if (isRemoveOnly) {
return await interaction.reply({
content: `**Failed to remove old status embed:**\n${(error as Error).message}`,
ephemeral: true
});
return await interaction.reply(
embedder.warning(`**Failed to remove old status embed:**\n${(error as Error).message}`, true)
);
}
}

Expand All @@ -239,10 +222,9 @@ export default async (interaction: CommandInteraction, txAdmin: TxAdmin) => {
try {
newStatusMessage = generateStatusMessage(txAdmin);
} catch (error) {
return await interaction.reply({
content: `**Failed to generate new embed:**\n${(error as Error).message}`,
ephemeral: true
});
return await interaction.reply(
embedder.warning(`**Failed to generate new embed:**\n${(error as Error).message}`, true)
);
}

//Attempt to send new message
Expand All @@ -255,14 +237,13 @@ export default async (interaction: CommandInteraction, txAdmin: TxAdmin) => {
newMessage.edit(newStatusMessage);
txAdmin.persistentCache.set('discord:status:channelId', interaction.channelId);
txAdmin.persistentCache.set('discord:status:messageId', newMessage.id);
return await interaction.reply({
content: `Embed saved!`,
ephemeral: true
});
} catch (error) {
return await interaction.reply({
content: `**Failed to send new embed:**\n${(error as Error).message}`,
ephemeral: true
});
return await interaction.reply(
embedder.warning(`**Failed to send new embed:**\n${(error as Error).message}`, true)
);
}

const msg = `Status embed saved.`;
logDiscordAdminAction(txAdmin, adminName, msg);
return await interaction.reply(embedder.success(msg));
}
103 changes: 103 additions & 0 deletions core/components/DiscordBot/commands/whitelist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
const modulename = 'DiscordBot:cmd:whitelist';
import { ColorResolvable, CommandInteraction, GuildMember, MessageEmbed, StaticImageURLOptions } from 'discord.js';
import logger from '@core/extras/console.js';
import TxAdmin from '@core/txAdmin';
import { now } from '@core/extras/helpers';
import { DuplicateKeyError } from '@core/components/PlayerDatabase';
import { embedder, ensurePermission, logDiscordAdminAction } from '../discordHelpers';
const { dir, log, logOk, logWarn, logError } = logger(modulename);


/**
* Command /whitelist member <mention>
*/
const handleMemberSubcommand = async (interaction: CommandInteraction, txAdmin: TxAdmin, adminName: string) => {
const avatarOptions: StaticImageURLOptions = { size: 64 };
const member = interaction.options.getMember('member', true) as GuildMember;

//Preparing player id/name/avatar
const identifier = `discord:${member.id}`;
const playerName = `${member.nickname ?? member.user.username}#${member.user.discriminator}`;
const playerAvatar = member.displayAvatarURL(avatarOptions) ?? member.user.displayAvatarURL(avatarOptions);

//Registering approval
try {
txAdmin.playerDatabase.registerWhitelistApprovals({
identifier,
playerName,
playerAvatar,
tsApproved: now(),
approvedBy: adminName,
});
} catch (error) {
return await interaction.reply(embedder.danger(`Failed to save whitelist approval: ${(error as Error).message}`));
}

const msg = `Added whitelist approval for ${playerName}.`;
logDiscordAdminAction(txAdmin, adminName, msg);
return await interaction.reply(embedder.success(msg));
}


/**
* Command /whitelist request <id>
*/
const handleRequestSubcommand = async (interaction: CommandInteraction, txAdmin: TxAdmin, adminName: string) => {
const input = interaction.options.getString('id', true);
const reqId = input.trim().toUpperCase();
if (reqId.length !== 5 || reqId[0] !== 'R') {
return await interaction.reply(embedder.danger('Invalid request ID.'));
}

//Find request
const requests = txAdmin.playerDatabase.getWhitelistRequests({ id: reqId });
if (!requests.length) {
return await interaction.reply(embedder.warning(`Whitelist request ID \`${reqId}\` not found.`));
}
const req = requests[0]; //just getting the first

//Register whitelistApprovals
const playerName = req.discordTag ?? req.playerDisplayName;
try {
txAdmin.playerDatabase.registerWhitelistApprovals({
identifier: `license:${req.license}`,
playerName,
playerAvatar: (req.discordAvatar) ? req.discordAvatar : null,
tsApproved: now(),
approvedBy: adminName,
});
} catch (error) {
if (!(error instanceof DuplicateKeyError)) {
return await interaction.reply(embedder.danger(`Failed to save wl approval: ${(error as Error).message}`));
}
}

//Remove record from whitelistRequests
try {
txAdmin.playerDatabase.removeWhitelistRequests({ id: reqId });
} catch (error) {
return await interaction.reply(embedder.danger(`Failed to remove wl request: ${(error as Error).message}`));
}

const msg = `Approved whitelist request \`${reqId}\` from ${playerName}.`;
logDiscordAdminAction(txAdmin, adminName, msg);
return await interaction.reply(embedder.success(msg));
}


/**
* Handler for /whitelist
*/
export default async (interaction: CommandInteraction, txAdmin: TxAdmin) => {
//Check permissions
const adminName = await ensurePermission(interaction, txAdmin, 'players.whitelist');
if (typeof adminName !== 'string') return;

const subcommand = interaction.options.getSubcommand();
if (subcommand === 'member') {
return await handleMemberSubcommand(interaction, txAdmin, adminName);
} else if (subcommand === 'request') {
return await handleRequestSubcommand(interaction, txAdmin, adminName);
}
throw new Error(`Subcommand ${subcommand} not found.`);
}
64 changes: 64 additions & 0 deletions core/components/DiscordBot/discordHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const modulename = 'DiscordBot:cmd';
import logger from '@core/extras/console.js';
import TxAdmin from "@core/txAdmin";
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
const { dir, log, logOk, logWarn, logError } = logger(modulename);


/**
* Generic embed generation functions
*/
const genericEmbed = (msg: string, ephemeral = false, color?: ColorResolvable, emoji?: string) => {
return {
ephemeral,
embeds: [new MessageEmbed({
color,
description: emoji ? `:${emoji}: ${msg}` : msg,
})],
}
}

export const embedder = {
generic: genericEmbed,
success: (msg: string, ephemeral = false) => genericEmbed(msg, ephemeral, '#0BA70B', 'white_check_mark'),
warning: (msg: string, ephemeral = false) => genericEmbed(msg, ephemeral, '#FFF100', 'warning'),
danger: (msg: string, ephemeral = false) => genericEmbed(msg, ephemeral, '#A70B28', 'no_entry_sign'),
}


/**
* Ensure that the discord interaction author has the required permission
*/
export const ensurePermission = async (interaction: CommandInteraction, txAdmin: TxAdmin, reqPerm: string) => {
const admin = txAdmin.adminVault.getAdminByProviderUID(interaction.user.id);
if (!admin) {
await interaction.reply(
embedder.warning('Your Discord ID is not registered in txAdmin :face_with_monocle:', true)
);
return false;
}
if (
admin.master !== true
&& !admin.permissions.includes('all_permissions')
&& !admin.permissions.includes(reqPerm)
) {
//@ts-ignore: not important
const permName = txAdmin.adminVault.registeredPermissions[reqPerm] ?? 'Unknown';
await interaction.reply(
embedder.danger(`You do not have the "${permName}" permissions.`, true)
);
return false;
}

return admin.name;
}


/**
* Equivalent to ctx.utils.logAction()
*/
export const logDiscordAdminAction = async (txAdmin: TxAdmin, adminName: string, message: string) => {
const logMessage = `[${adminName}] ${message}`;
log(logMessage);
txAdmin.logger.admin.write(logMessage);
}
10 changes: 5 additions & 5 deletions core/components/DiscordBot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export default class DiscordBot {
}

//Check for guild id
if(typeof this.config.guild !== 'string' || !this.config.guild.length){
if (typeof this.config.guild !== 'string' || !this.config.guild.length) {
return sendError('Discord bot enabled while guild id is not set.');
}

Expand All @@ -188,18 +188,16 @@ export default class DiscordBot {
//Setup Ready listener
this.#client.on('ready', async () => {
if (!this.#client?.isReady()) throw new Error(`ready event while not being ready`);
// logOk(`Started and logged in as '${this.client?.user?.tag}'`);
this.updateStatus().catch();

//Fetching guild
const guild = this.#client.guilds.cache.find((guild) => guild.id === this.config.guild);
if (!guild){
if (!guild) {
return sendError(`Discord bot could not resolve guild id ${this.config.guild}`);
}
this.guild = guild;

//Fetching announcements channel
if(this.config.announceChannel){
if (this.config.announceChannel) {
const fetchedChannel = this.#client.channels.cache.find((x) => x.id === this.config.announceChannel);
if (!fetchedChannel) {
return sendError(`Channel ${this.config.announceChannel} not found.`);
Expand All @@ -211,6 +209,8 @@ export default class DiscordBot {
}

this.#client.application.commands.set(slashCommands);
logOk(`Started and logged in as '${this.#client.user.tag}'`);
this.updateStatus().catch();

return resolve();
});
Expand Down
8 changes: 6 additions & 2 deletions core/components/DiscordBot/interactionCreateHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import { Interaction } from 'discord.js';
import TxAdmin from '@core/txAdmin.js';
import logger, { ogConsole } from '@core/extras/console.js';
import statusCommandHandler from './commands/status';
import whitelistCommandHandler from './commands/whitelist';
import { cloneDeep } from 'lodash-es'; //DEBUG
import { embedder } from './discordHelpers';
const { dir, log, logOk, logWarn, logError, logDebug } = logger(modulename);


//All commands
const handlers = {
status: statusCommandHandler,
whitelist: whitelistCommandHandler,
}

const noHandlerResponse = async (interaction: Interaction) => {
Expand Down Expand Up @@ -63,8 +66,9 @@ export default async (txAdmin: TxAdmin, interaction: Interaction) => {
await handler(interaction, txAdmin);
return;
} catch (error) {
logError(`Error executing ${interaction.commandName}: ${(error as Error).message}`);
return;
const msg = `Error executing ${interaction.commandName}: ${(error as Error).message}`;
logError(msg);
return await interaction.reply(embedder.danger(msg, true));
}
}

Expand Down
38 changes: 38 additions & 0 deletions core/components/DiscordBot/slash.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ApplicationCommandDataResolvable } from 'discord.js';
import { ApplicationCommandOptionTypes } from 'discord.js/typings/enums';
import { ApplicationCommandOptionType, ApplicationCommandType } from './extractedEnums';


Expand All @@ -20,9 +21,46 @@ const statusCommand: ApplicationCommandDataResolvable = {
]
}

const whitelistCommand: ApplicationCommandDataResolvable = {
type: ApplicationCommandType.ChatInput as number,
name: 'whitelist',
description: 'Status embed commands;',
options: [
{
type: ApplicationCommandOptionType.Subcommand as number,
name: 'member',
description: 'Adds a member to the whitelist approvals.',
options: [
{
type: 'USER',
name: 'member',
description: 'The member that will be whitelisted.',
required: true,
}
]
},
{
type: ApplicationCommandOptionType.Subcommand as number,
name: 'request',
description: 'Approves a whitelist request ID (eg R1234).',
options: [
{
type: 'STRING',
name: 'id',
description: 'The ID of the request (eg R1234).',
required: true,
minLength: 5,
maxLength: 5,
}
]
}
]
}

/**
* Exported commands
*/
export default [
statusCommand,
whitelistCommand,
] as ApplicationCommandDataResolvable[];
2 changes: 1 addition & 1 deletion core/webroutes/whitelist/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ async function handleRequests(ctx: Context, action: any): Promise<GenericApiResp
const req = requests[0]; //just getting the first

//Register whitelistApprovals
const playerName = req.discordTag ?? req.playerDisplayName
const playerName = req.discordTag ?? req.playerDisplayName;
try {
playerDatabase.registerWhitelistApprovals({
identifier: `license:${req.license}`,
Expand Down
Loading

0 comments on commit 6f49bc7

Please sign in to comment.