Adapter system for integrating with different chat platforms, including built-in Shell and Campfire adapters and extensibility patterns.
The Adapter class provides the interface between hubot and chat services, handling message sending and receiving.
/**
* Base adapter class for chat service integration
* @param robot - Robot instance that owns this adapter
*/
class Adapter {
constructor(robot: Robot);
// Properties
robot: Robot;
// Abstract methods (must implement in subclasses)
async send(envelope: Envelope, ...strings: string[]): Promise<void>;
async reply(envelope: Envelope, ...strings: string[]): Promise<void>;
async topic(envelope: Envelope, ...strings: string[]): Promise<void>;
async play(envelope: Envelope, ...strings: string[]): Promise<void>;
async run(): Promise<void>;
close(): void;
// Default implementations (can override in subclasses)
async emote(envelope: Envelope, ...strings: string[]): Promise<void>;
// Helper methods
async receive(message: Message): Promise<void>;
}Usage Example - Creating Custom Adapter:
import { Adapter, TextMessage } from "hubot";
class MyCustomAdapter extends Adapter {
constructor(robot) {
super(robot);
this.client = null;
}
async run() {
// Initialize connection to chat service
this.client = new MyChatClient({
token: process.env.CHAT_TOKEN,
onMessage: (data) => this.handleMessage(data)
});
await this.client.connect();
this.robot.logger.info("MyCustomAdapter connected");
}
async send(envelope, ...strings) {
for (const string of strings) {
await this.client.sendMessage(envelope.room, string);
}
}
async reply(envelope, ...strings) {
const user = envelope.user.name;
for (const string of strings) {
await this.client.sendMessage(envelope.room, `${user}: ${string}`);
}
}
async emote(envelope, ...strings) {
for (const string of strings) {
await this.client.sendEmote(envelope.room, string);
}
}
handleMessage(data) {
const user = this.robot.brain.userForId(data.userId, {
name: data.userName,
room: data.room
});
const message = new TextMessage(user, data.text, data.id);
this.receive(message);
}
close() {
if (this.client) {
this.client.disconnect();
}
}
}
// Use custom adapter
const robot = new Robot(new MyCustomAdapter(), true, "MyBot");Interactive command-line adapter for local development and testing.
/**
* Shell adapter for interactive command-line usage
* Provides readline interface with command completion and history
*/
class Shell extends Adapter {
constructor(robot: Robot);
// Built-in commands
// \q - quit
// \? - help
// \c - clear screen
}Environment Variables:
HUBOT_SHELL_HISTSIZEHUBOT_SHELL_USER_IDHUBOT_SHELL_USER_NAMEUsage Example:
import { Robot } from "hubot";
// Create bot with Shell adapter
const robot = new Robot("Shell", true, "DevBot");
robot.hear(/hello/i, (res) => {
res.send("Hello from the shell!");
});
await robot.run();
// Now you can interact via command line:
// > hello
// DevBot> Hello from the shell!Integration with 37signals Campfire chat service.
/**
* Campfire adapter for 37signals Campfire chat
* Supports real-time streaming, room management, and file uploads
*/
class Campfire extends Adapter {
constructor(robot: Robot);
// Additional Campfire-specific methods
async lock(envelope: Envelope, ...strings: string[]): Promise<void>;
async unlock(envelope: Envelope): Promise<void>;
}Environment Variables:
HUBOT_CAMPFIRE_TOKENHUBOT_CAMPFIRE_ROOMSHUBOT_CAMPFIRE_ACCOUNTUsage Example:
// Set environment variables first
process.env.HUBOT_CAMPFIRE_TOKEN = "your-token";
process.env.HUBOT_CAMPFIRE_ROOMS = "12345,67890";
process.env.HUBOT_CAMPFIRE_ACCOUNT = "your-account";
const robot = new Robot("Campfire", true, "CampfireBot");
// Campfire-specific functionality
robot.hear(/lock room/i, (res) => {
res.locked("Room is now locked!");
});
await robot.run();The envelope provides context for sending messages, containing room and user information.
/**
* Message envelope containing delivery context
*/
interface Envelope {
room: string; // Room/channel identifier
user: User; // User who sent the original message
message: Message; // Original message that triggered response
}Usage in Adapters:
// In adapter send method
async send(envelope, ...strings) {
const room = envelope.room;
const user = envelope.user;
for (const string of strings) {
await this.chatService.sendToRoom(room, string);
this.robot.logger.info(`Sent to ${room}: ${string}`);
}
}Adapters can emit events for monitoring and debugging.
// Adapter lifecycle events
adapter.emit('connected');
adapter.emit('disconnected');
// Message events
adapter.emit('send', envelope, string);
adapter.emit('reply', envelope, string);
// Error events
adapter.emit('error', error);
// Listen for adapter events
robot.adapter.on('connected', () => {
robot.logger.info('Adapter connected successfully');
});
robot.adapter.on('error', (error) => {
robot.logger.error('Adapter error:', error);
});Understanding how messages flow through the adapter system.
// 1. Chat service receives message
// 2. Adapter creates Message object
const message = new TextMessage(user, text, id);
// 3. Adapter calls receive() to send to robot
await this.receive(message);
// 4. Robot processes through middleware and listeners
// 5. Listener creates Response object
// 6. Response methods call back to adapter
await adapter.send(envelope, "Response text");Common patterns for configuring adapters with environment variables and options.
class MyAdapter extends Adapter {
constructor(robot) {
super(robot);
// Required environment variables
this.token = process.env.MY_ADAPTER_TOKEN;
if (!this.token) {
throw new Error("MY_ADAPTER_TOKEN environment variable required");
}
// Optional configuration with defaults
this.timeout = parseInt(process.env.MY_ADAPTER_TIMEOUT) || 30000;
this.retries = parseInt(process.env.MY_ADAPTER_RETRIES) || 3;
// Configuration validation
this.validateConfig();
}
validateConfig() {
if (this.timeout < 1000) {
throw new Error("Timeout must be at least 1000ms");
}
}
}How to use external adapter packages from npm.
// Install external adapter
// npm install @hubot-friends/hubot-slack
// Use with adapter name
const robot = new Robot("@hubot-friends/hubot-slack", true, "SlackBot");
// Or with adapter path
const robot = new Robot("/path/to/my-adapter.js", true, "CustomBot");
// Set required environment variables
process.env.HUBOT_SLACK_TOKEN = "xoxb-your-token";
await robot.run();interface AdapterConstructor {
new (robot: Robot): Adapter;
}
interface Envelope {
room: string;
user: User;
message: Message;
}
interface AdapterOptions {
timeout?: number;
retries?: number;
[key: string]: any;
}