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
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_HISTSIZE - Command history size (default: 1024)HUBOT_SHELL_USER_ID - User ID for shell user (default: "1")HUBOT_SHELL_USER_NAME - User name for shell user (default: "Shell")Usage 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_TOKEN - Campfire API authentication tokenHUBOT_CAMPFIRE_ROOMS - Comma-separated list of room IDs to joinHUBOT_CAMPFIRE_ACCOUNT - Campfire account nameUsage 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;
}