A comprehensive chat bot framework modeled after GitHub's Campfire bot for building extensible chat bots across multiple platforms
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Dynamic script loading from files and npm packages, plus middleware system for customizing message processing pipeline.
Hubot supports dynamic loading of bot functionality from local files and external npm packages.
/**
* Load a single script file
* Supports .js, .mjs, and .ts extensions
* @param filepath - Full path to script file
* @param filename - Name of the file for logging purposes
*/
async loadFile(filepath: string, filename: string): Promise<any>;
/**
* Load all scripts from a directory
* Recursively loads all supported script files
* @param path - Directory path containing scripts
*/
async load(path: string): Promise<any[]>;
/**
* Load external scripts from npm packages
* @param packages - Array of npm package names or object with package configs
*/
async loadExternalScripts(packages: string[] | object): Promise<void>;Usage Examples:
import { Robot } from "hubot";
const robot = new Robot("Shell");
// Load single script file
await robot.loadFile("./scripts/custom-commands.js", "custom-commands.js");
// Load all scripts from directory
await robot.load("./scripts");
// Load external npm packages
await robot.loadExternalScripts([
"hubot-help",
"hubot-diagnostics",
"@hubot-friends/hubot-pugme",
"hubot-rules"
]);
await robot.run();Scripts are modules that export a function which receives the robot instance and adds listeners.
/**
* Standard script export function
* @param robot - Robot instance to configure
*/
export default function(robot: Robot): void {
// Add listeners, configure robot, etc.
}
// CommonJS format
module.exports = function(robot) {
// Script functionality
};Example Script:
// scripts/weather.js
export default function(robot) {
// Simple weather command
robot.respond(/weather (.+)/i, async (res) => {
const location = res.match[1];
try {
const weather = await getWeather(location);
res.send(`Weather in ${location}: ${weather.description}, ${weather.temp}°C`);
} catch (error) {
res.send(`Sorry, couldn't get weather for ${location}`);
}
});
// Help documentation
robot.commands.push("weather <location> - Get weather for location");
}
async function getWeather(location) {
// Weather API integration
const response = await fetch(`https://api.weather.com/v1/current?location=${location}`);
return response.json();
}Middleware provides hooks into the message processing pipeline for custom behavior.
/**
* Register listener middleware (runs before listeners are matched)
* @param middleware - Middleware function
*/
listenerMiddleware(middleware: MiddlewareFunction): void;
/**
* Register response middleware (runs before responses are sent)
* @param middleware - Middleware function
*/
responseMiddleware(middleware: MiddlewareFunction): void;
/**
* Register receive middleware (runs when messages are first received)
* @param middleware - Middleware function
*/
receiveMiddleware(middleware: MiddlewareFunction): void;
/**
* Middleware function signature
* @param context - Middleware context object
* @param next - Function to call to continue processing
* @param done - Function to call when middleware is complete
*/
type MiddlewareFunction = (context: any, next: () => void, done: () => void) => void;Usage Examples:
// Logging middleware
robot.receiveMiddleware((context, next, done) => {
robot.logger.info(`Received message: ${context.response.message.text}`);
next(); // Continue processing
done(); // Mark middleware complete
});
// Authentication middleware
robot.listenerMiddleware((context, next, done) => {
const user = context.response.message.user;
if (context.listener.options.requireAuth && !user.get('authenticated')) {
context.response.send("You must authenticate first!");
done(); // Stop processing - don't call next()
return;
}
next(); // Continue to listener
done(); // Mark middleware complete
});
// Rate limiting middleware
const rateLimits = new Map();
robot.responseMiddleware((context, next, done) => {
const userId = context.response.message.user.id;
const now = Date.now();
const lastMessage = rateLimits.get(userId) || 0;
if (now - lastMessage < 1000) { // 1 second rate limit
done(); // Skip response - don't call next()
return;
}
rateLimits.set(userId, now);
next(); // Send response
done(); // Mark middleware complete
});
// Response transformation middleware
robot.responseMiddleware((context, next, done) => {
// Add emoji to all responses
if (context.string) {
context.string = `🤖 ${context.string}`;
}
next(); // Continue processing
done(); // Mark middleware complete
});The context object passed to middleware contains information about the current message processing.
interface MiddlewareContext {
response: Response; // Response object
listener?: Listener; // Current listener (listener middleware only)
string?: string; // Response string (response middleware only)
}External scripts can be configured through package.json and environment variables.
// package.json
{
"dependencies": {
"hubot-help": "^1.0.0",
"hubot-diagnostics": "^1.0.0"
},
"hubot": {
"external-scripts": [
"hubot-help",
"hubot-diagnostics"
]
}
}
// Load from package.json
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
await robot.loadExternalScripts(packageJson.hubot['external-scripts']);Scripts should provide help documentation that integrates with hubot's help system.
// Add commands to robot.commands array
robot.commands.push("ping - Reply with pong");
robot.commands.push("time - Display current time");
robot.commands.push("echo <message> - Echo back the message");
// Help command will automatically display all commands
robot.respond(/help$/i, (res) => {
const commands = robot.commands.map(cmd => `• ${cmd}`).join('\n');
res.send(`Available commands:\n${commands}`);
});Best practices for error handling in scripts and middleware.
// Script-level error handling
export default function(robot) {
robot.respond(/risky command/i, async (res) => {
try {
const result = await riskyOperation();
res.send(`Success: ${result}`);
} catch (error) {
robot.logger.error('Risky command failed:', error);
res.send("Sorry, something went wrong!");
}
});
// Global error handler for this script
robot.error((error) => {
robot.logger.error('Script error:', error);
});
}
// Middleware error handling
robot.listenerMiddleware((context, next, done) => {
try {
// Middleware logic
next(); // Continue processing
done(); // Mark complete
} catch (error) {
context.response.robot.logger.error('Middleware error:', error);
done(); // Stop processing on error - don't call next()
}
});Loading scripts based on environment or configuration.
// Load different scripts for different environments
if (process.env.NODE_ENV === 'production') {
await robot.loadExternalScripts(['hubot-monitoring', 'hubot-alerts']);
} else {
await robot.loadExternalScripts(['hubot-diagnostics', 'hubot-dev-tools']);
}
// Load scripts based on adapter
if (robot.adapterName === 'Slack') {
await robot.loadExternalScripts(['hubot-slack-reactions']);
} else if (robot.adapterName === 'Discord') {
await robot.loadExternalScripts(['hubot-discord-features']);
}The Middleware class manages middleware stacks for different processing stages.
/**
* Middleware stack management
* @param robot - Robot instance
*/
class Middleware {
constructor(robot: Robot);
robot: Robot;
stack: MiddlewareFunction[];
/**
* Execute middleware stack with context
* @param context - Context object for middleware
* @returns Promise resolving to boolean (true to continue, false to stop)
*/
async execute(context: MiddlewareContext): Promise<boolean>;
/**
* Register new middleware function
* @param middleware - Function to add to stack
*/
register(middleware: MiddlewareFunction): void;
}type MiddlewareFunction = (context: any, next: () => void, done: () => void) => void;
interface MiddlewareContext {
response: Response;
listener?: Listener;
string?: string;
}
interface ScriptModule {
default?: (robot: Robot) => void;
(robot: Robot): void; // CommonJS export
}
interface ExternalScriptConfig {
'external-scripts': string[];
}