Skip to content

Commit

Permalink
feat: auto-generation of the command table (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
benw202 authored Jan 25, 2024
1 parent 7106ee3 commit edfaf7e
Show file tree
Hide file tree
Showing 7 changed files with 1,251 additions and 304 deletions.
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,10 @@ MONGODB_URL=mongodb://admin:1234@localhost:27017/fbw?authSource=admin
IMAGE_BASE_URL=https://assets.discord.flybywirecdn.com/assets/images
IMAGE_BASE_URL=https://assets-staging.discord.flybywirecdn.com/assets/images
IMAGE_BASE_URL=YourOwnLocationForLocalDev

# You will need the following to upload to cloudflare

CLOUDFLARE_BUCKET_NAME=BUCKET_NAME
CLOUDFLARE_ACCOUNT_ID=TOKEN
CLOUDFLARE_ACCESS_KEY_ID=TOKEN
CLOUDFLARE_SECRET_ACCESS_KEY=TOKEN
1 change: 1 addition & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

Update <small>_ January 2024</small>

- feat: auto-generation of the command table (24/01/2024)
- feat: migrate reportedIssues command (22/01/2024)
- feat: docSearch command (22/01/2024)
- fix: vatsim events max fields (20/01/2024)
Expand Down
2 changes: 1 addition & 1 deletion .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ You can find the pull request template [here](PULL_REQUEST_TEMPLATE.md).

The `config` folder contains the configuration details for running the bot in the staging or production environment.

The config contains different variables and settings which are used by the bot to reference channels, check permissions, use colors, etc. All these valies are defined based on the Discord server the bot will connect to and are unique to that server.
The config contains different variables and settings which are used by the bot to reference channels, check permissions, use colors, etc. All these values are defined based on the Discord server the bot will connect to and are unique to that server.

You can create your own config json file by copying one of the existing ones and updating the different constants/settings with your own values. This allows you to run the bot in your own environment without having to recompile it from the source.

Expand Down
1,424 changes: 1,121 additions & 303 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"author": "FlyByWire Simulations",
"license": "AGPL-3.0",
"dependencies": {
"@aws-sdk/client-s3": "^3.499.0",
"@aws-sdk/lib-storage": "^3.499.0",
"@hokify/agenda": "^6.0.0",
"@octokit/request": "^8.1.1",
"bad-words": "^3.0.4",
Expand Down
2 changes: 2 additions & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import vatsim from './utils/vatsim/vatsim';
import help from './utils/help';
import docSearch from './utils/docSearch';
import reportedIssues from './utils/reportedIssues';
import commandTable from './moderation/commandTable';

const commandArray: SlashCommand[] = [
ping,
Expand Down Expand Up @@ -57,6 +58,7 @@ const commandArray: SlashCommand[] = [
help,
docSearch,
reportedIssues,
commandTable,
];

export default commandArray;
117 changes: 117 additions & 0 deletions src/commands/moderation/commandTable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { ApplicationCommandOptionType, ApplicationCommandType, CommandInteraction } from 'discord.js';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { slashCommand, slashCommandStructure, Logger, constantsConfig } from '../../lib';

const data = slashCommandStructure({
name: 'generate_command_table',
description: 'Generates the command table.',
type: ApplicationCommandType.ChatInput,
default_member_permissions: constantsConfig.commandPermission.MANAGE_SERVER, //Overrides need to be added for admin, moderator bot developer and docs team roles
dm_permission: false,
});

export default slashCommand(data, async ({ interaction }: { interaction: CommandInteraction }) => {
try {
// Fetch all commands from the API based on the environment
let commands;

if (process.env.NODE_ENV === 'production') {
commands = await interaction.client.application?.commands.fetch();
} else {
commands = await interaction.guild?.commands.fetch();
}

// Check if the commands were fetched successfully
if (!commands) {
return interaction.reply({
content: 'An error occurred while fetching commands.',
ephemeral: true,
});
}

// Convert the iterable of commands into an array
const commandArray = Array.from(commands.values());

// Sort the commands alphabetically by name
const sortedCommands = commandArray.sort((a, b) => a.name.localeCompare(b.name));

// Build the Markdown table
let table = '## Bot Commands\n\n| Command | Description | Subcommand and Groups |\n| --- | --- | --- |\n';

for (const command of sortedCommands) {
// eslint-disable-next-line prefer-const
let { name, description, type } = command;

const subcommandList = command.options?.filter((option) => option.type === ApplicationCommandOptionType.Subcommand || option.type === ApplicationCommandOptionType.SubcommandGroup);

let subcommandDescription = '';

if (subcommandList && subcommandList.length > 0) {
subcommandDescription = subcommandList
.map((subcommand) => {
if (subcommand.type === ApplicationCommandOptionType.Subcommand) {
return subcommand.name;
}
if (subcommand.type === ApplicationCommandOptionType.SubcommandGroup) {
const groupSubcommands = subcommand.options?.filter((sub) => sub.type === ApplicationCommandOptionType.Subcommand);
if (groupSubcommands && groupSubcommands.length > 0) {
return `${subcommand.name} [${groupSubcommands
.map((sub) => sub.name)
.join(', ')}]`;
}
return `${subcommand.name} [None]`;
}
return '';
})
.join(', ');
}

// Denote context-specific commands in the description
if (type === ApplicationCommandType.User || type === ApplicationCommandType.Message) {
description += `(Context Command - ${type === ApplicationCommandType.User ? 'User' : 'Message'})`;
}

// Append subcommands to the Subcommands column
table += `| ${name} | ${description} | ${subcommandDescription} |\n`;
}

// Upload the file to Cloudflare R2
const uploadParams = {
Bucket: process.env.CLOUDFLARE_BUCKET_NAME,
Key: 'utils/commands_table.md',
Body: table,
};

try {
const S3 = new S3Client({
region: 'auto',
endpoint: `https://${process.env.CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.CLOUDFLARE_ACCESS_KEY_ID!,
secretAccessKey: process.env.CLOUDFLARE_SECRET_ACCESS_KEY!,
},
});

const commandTableUpload = new PutObjectCommand(uploadParams);
await S3.send(commandTableUpload);
Logger.info('Successfully uploaded commands table to Cloudflare R2.');
} catch (error) {
Logger.error(error);
return interaction.reply({
content: 'An error occurred while uploading to Cloudflare R2.',
ephemeral: true,
});
}

return interaction.reply({
content: 'Markdown table has been saved and uploaded to Cloudflare R2.',
ephemeral: true,
});
} catch (error) {
Logger.error(error);
return interaction.reply({
content: 'An error occurred while generating the Markdown file.',
ephemeral: true,
});
}
});

0 comments on commit edfaf7e

Please sign in to comment.