or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

adapters-integration.mdbrain-user-management.mdcli.mdcore-bot-management.mddatastore.mdindex.mdmessage-handling.mdscripts-middleware.md
tile.json

scripts-middleware.mddocs/

Scripts and Middleware

Dynamic script loading from files and npm packages, plus middleware system for customizing message processing pipeline.

Capabilities

Script Loading

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();

Script Structure

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 System

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
});

Middleware Context

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 Script Configuration

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']);

Script Documentation and Help

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}`);
});

Error Handling in Scripts

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()
  }
});

Conditional Script Loading

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']);
}

Middleware Class

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;
}

Types

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[];
}