Skip to content

Commit

Permalink
Merge pull request #120 from GlueOps/chore/refactor-code
Browse files Browse the repository at this point in the history
Chore/refactor code
  • Loading branch information
NichArchA82 authored Jan 8, 2025
2 parents 5a9d8ea + 9b56cc8 commit b85772a
Show file tree
Hide file tree
Showing 18 changed files with 247 additions and 27 deletions.
17 changes: 17 additions & 0 deletions bot/src/commands/button.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
/*
This is a sample command that creates a button in the slack message.
The button click event is handled by the button method.
*/

import logger from "command-handler/src/util/logger.js";

//initialize logger
const log = logger();

export default {
description: 'creates a button',

//button click event handler This is called when the button is clicked
button: ({ handler, body, response }) => {
log.info("Clicked the button", body.user)
response({
text: `<@${body.user.id}> clicked the Button`
})
},

/*
run method is called when the command is executed
To execute this command type !button in the slack channel where the bot is installed
The parameters are destructured from the object passed to the run method
response is used to send the response back to the slack channel
event is the event object that contains the event details from slack.
*/
run: ({ response, event }) => {

response({
//blocks is used to create a button in the slack message
blocks: [
{
"type": "section",
Expand All @@ -32,6 +48,7 @@ export default {
}
}
],
//text is fallback text that is displayed when the message is not supported
text: `Hey there <@${event.user}>!`
})
}
Expand Down
9 changes: 8 additions & 1 deletion bot/src/commands/hello.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
This is a simple command that replies with "Hey there <@user>!" after 5 seconds.
*/

//delay function to simulate a delay
const delay = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms));
}
Expand All @@ -6,10 +11,12 @@ export default {
description: 'Replies with Hello',

run: async ({ response, event }) => {
//call the delay function to simulate a delay of 5 seconds.
await delay(5000);

//send the response back to the slack channel After the delay
response({
text: `Hey there <@${event.user}>!`
})
});
}
}
4 changes: 4 additions & 0 deletions bot/src/commands/ping.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/*
This is a simple command that replies with pong
*/

export default {
description: 'Replies with pong',

Expand Down
12 changes: 10 additions & 2 deletions bot/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@ const customLogger = {
error: (message) => log.error(message),
};

//receiver to integrate express with bolt
/*
receiver to integrate express with bolt
The receiver is what allows Bolt to communicate with Slack's servers.
The receiver listens for incoming HTTP requests from Slack, and then
forwards them to the Bolt app.
*/
const receiver = new ExpressReceiver({
signingSecret: process.env.SIGNING_SECRET,
});

// Initialize Bolt app with custom logger and receiver
const app = new App({
token: process.env.BOT_TOKEN,
receiver,
Expand All @@ -30,11 +36,13 @@ const app = new App({
logger: customLogger,
});

//function that starts the server and initializes the command handler
(async () => {
server(receiver);
new CH({
//passing the bolt app to the command handler
app,
featuresDir: path.join(process.cwd(), 'src', 'features'),
//directory where the commands are located
commandsDir: path.join(process.cwd(), 'src', 'commands'),
});
})();
16 changes: 15 additions & 1 deletion command-handler/src/cmd-handler/buttons.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
/*
This file contains the list of buttons that are registered with the command handler.
This list tells the button handler, which command should handle the button click.
This list contains both regex patterns and static button names.
The static are defined by the programmer while the regex patterns are generated by the command handler.
Each button is a JSON object with the button name as the object name with a key value pair of command
and the command name that should handle the button click.
*/

const registeredButtons = {
//static buttons that are defined by the programmer
button_list_servers: {
command: 'vm',
},
Expand All @@ -8,7 +18,11 @@ const registeredButtons = {
button_click: {
command: 'button',
},
//regex patterns to match generated buttons
/*
regex patterns to match generated buttons
The regex matches the buttons at the start of the string,
with the trailing _ not being the end of the string in the match
*/
"^button_start_": {
command: 'vm',
isRegex: true,
Expand Down
43 changes: 42 additions & 1 deletion command-handler/src/cmd-handler/command-handler.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
This file is responsible for handling commands.
It reads all the commands from the commands directory and stores them in a map.
*/

import path from 'path';
import { fileURLToPath, pathToFileURL } from 'url';
import logger from '../util/logger.js';
Expand All @@ -10,49 +15,85 @@ export default class CommandHandler {
_commands = new Map();

constructor(commandsDir, handler) {
//throw error if commands directory is not provided
if (!commandsDir) throw new Error('Commands directory is required');

//initialize the command handler
//this function is called because the constructor cannot be async
this.init(commandsDir, handler);
}

//getter for commands
get commands() {
return this._commands;
}

async init(commandsDir, handler) {
const registeredCommands = [];
//get all the files in the commands directory
const botCommands = getFiles(commandsDir);
//get the current file path and directory
const currentFilePath = fileURLToPath(import.meta.url);
const currentFileDir = path.dirname(currentFilePath);
//get all the files in the built-in commands directory
const builtInCommands = getFiles(path.join(currentFileDir, '..', 'commands'));
//combine the bot commands and built-in commands
const commands = [...botCommands, ...builtInCommands];

//loop through all the commands
for (const command of commands) {
//get the command name and file path
const commandName = path.basename(command, '.js').toLowerCase();
const filePath = pathToFileURL(command);
//import the command object
const commandObject = (await import(filePath)).default;

//warn if the command is empty and continue to the next command
if (!commandObject) {
log.warn(`Command ${commandName} is empty`);
continue;

}

//destructure the command object properties
const {
aliases = [],
delete: del,
init = () => {},
} = commandObject;

/*
if del (delete) is true, continue to the next command
skipping the command registration process. This does
not delete the command file from the app, simply does
not register the command with the command handler
*/
if (del) {
continue;
}

/*
call the init function that was destructured
from the command object. Since the init function
is async, we await for the function to complete before
continuing. This function is an optional function
that commands can use to run any initialization code
that the command depends on before being registered.
*/
await init(handler)

/*
set the command name and object in the commands map
and add the command name to the registered commands array
so the app is aware of the commands that are registered
*/
registeredCommands.push(commandName);
this._commands.set(commandName, commandObject);

/*
loop through the aliases and set the alias and command object
so commands can be called by their aliases in addition to their
command name. This allows for more flexibility when calling commands
*/
for (const alias of aliases) {
this._commands.set(alias, commandObject);
}
Expand Down
22 changes: 16 additions & 6 deletions command-handler/src/cmd-handler/run-button.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
/*
This file is responsible for running the button
That is associated with a command
*/

export default async ({
commandName,
actionId,
handler,
app,
body,
say,
commandName, // the name of the command
actionId, // the action ID of the button
handler, // the command handler
app, // the slack app
body, // the body of the event
say, // the say function to send a message
}) => {
// destructure the commands and command handler from the handler
const { commandHandler } = handler;
const { commands } = commandHandler;

//retrieve the command object from the commands map
const command = commands.get(commandName);

//create a response function to send a message
const response = (obj) => {
say(obj)
};

//check if the command does not exist, or has no button handler, if so return.
if (!command || !command.button) {
response({
text: `No button handler is registered.`
})
return
}

// run the command's button handler
command.button({ handler, app, body, response, actionId });
};
22 changes: 16 additions & 6 deletions command-handler/src/cmd-handler/run-command.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
/*
This file is responsible for running the command
*/

export default async ({
commandName,
handler,
app,
event,
args,
say
commandName, // the name of the command
handler, // the command handler
app, // the slack app
event, // the event object
args, // the command arguments
say // the say function to send a message
}) => {
// destructure the commands and command handler from the handler
const { commandHandler } = handler;
const { commands } = commandHandler;

//retrieve the command object from the commands map
const command = commands.get(commandName);

//check if the command does not exist, or has no run function, if so return.
if (!command || !command.run) {
return
}

// retrieve the text from the arguments joined by a space
const text = args.join(' ');

//create a response function to send a message
const response = (obj) => {
say(obj)
};

// run the command's run function
command.run({ handler, app, event, response, text, args });
};
18 changes: 17 additions & 1 deletion command-handler/src/events/button-click.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
/*
This file is responsible for handling button click events.
*/

import runButton from "../cmd-handler/run-button.js";
import registeredButtons from '../cmd-handler/buttons.js';

export default function button(app, handler) {

// Register a single action handler for all actions
app.action(/button_/, async ({ body, ack, say }) => {
// Acknowledge the action so the app doesn't timeout
await ack();

// Extract the action ID from the event body
Expand All @@ -13,17 +18,28 @@ export default function button(app, handler) {
let commandName = null;
// Check for an exact match first
if (registeredButtons[actionId]) {
// set the command to handle the button to the matched button
commandName = registeredButtons[actionId].command;
} else {
// If no exact match, check for a regex pattern match
/*
Iterate all of the registered buttons to check for a regex match
destructuring the pattern and config from the registeredButtons object
*/
for (const [pattern, config] of Object.entries(registeredButtons)) {
/*
check if the registered button is a regex pattern and
if the pattern matches the action ID
*/
if (config.isRegex && new RegExp(pattern).test(actionId)) {
// set the command to handle the button to the matched button
commandName = config.command;
// break out of the loop if a match is found
break;
}
}
}

// call the runButton function to run the button handler
runButton({
commandName,
actionId,
Expand Down
Loading

0 comments on commit b85772a

Please sign in to comment.