@smartuy/builderbot-provider-waha
Version:
WAHA (WhatsApp HTTP API) provider for BuilderBot desarrollado por SmartUY
399 lines (332 loc) • 12.2 kB
JavaScript
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const args = process.argv.slice(2);
const command = args[0];
const projectName = args[1];
// Help message
if (!command || command === '--help' || command === '-h') {
console.log(`
@smartuy/builderbot-provider-waha CLI
Usage:
npx @smartuy/builderbot-provider-waha init <project-name>
Commands:
init <project-name> Create a new BuilderBot project with WAHA provider
--help, -h Show this help message
`);
process.exit(0);
}
// Init command
if (command === 'init') {
if (!projectName) {
console.error('Error: Project name is required');
console.log('Usage: npx @smartuy/builderbot-provider-waha init <project-name>');
process.exit(1);
}
const projectDir = path.join(process.cwd(), projectName);
// Create project directory
console.log(`Creating project directory: ${projectName}`);
if (fs.existsSync(projectDir)) {
console.error(`Error: Directory ${projectName} already exists`);
process.exit(1);
}
fs.mkdirSync(projectDir, { recursive: true });
// Create package.json
console.log('Creating package.json...');
const packageJson = {
name: projectName,
version: '1.0.0',
description: 'WhatsApp bot using BuilderBot and WAHA provider',
main: 'dist/app.js',
scripts: {
build: 'tsc',
start: 'node dist/app.js',
dev: 'concurrently "tsc --watch" "nodemon dist/app.js"'
},
dependencies: {
'@builderbot/bot': '^1.2.5',
'@smartuy/builderbot-provider-waha': 'latest',
axios: '^1.8.4',
dotenv: '^16.4.7',
express: '^4.21.2'
},
devDependencies: {
'@types/express': '^5.0.1',
'@types/node': '^22.13.11',
concurrently: '^7.0.0',
nodemon: '^2.0.0',
typescript: '^5.8.2'
}
};
fs.writeFileSync(
path.join(projectDir, 'package.json'),
JSON.stringify(packageJson, null, 2)
);
// Create tsconfig.json
console.log('Creating tsconfig.json...');
const tsConfig = {
compilerOptions: {
target: 'ES2020',
module: 'commonjs',
esModuleInterop: true,
forceConsistentCasingInFileNames: true,
strict: true,
skipLibCheck: true,
outDir: 'dist',
rootDir: 'src'
},
include: ['src/**/*']
};
fs.writeFileSync(
path.join(projectDir, 'tsconfig.json'),
JSON.stringify(tsConfig, null, 2)
);
// Create src directory
console.log('Creating src directory...');
fs.mkdirSync(path.join(projectDir, 'src'), { recursive: true });
// Create app.ts
console.log('Creating app.ts...');
const appContent = `import express from 'express';
import { createBot, createFlow, MemoryDB, addKeyword, EVENTS } from '@builderbot/bot';
import { WahaProvider } from '@smartuy/builderbot-provider-waha';
import dotenv from 'dotenv';
// Load environment variables
dotenv.config();
// Create Express app
const app = express();
app.use(express.json());
// Define welcome flow
const welcomeFlow = addKeyword(['hi', 'hello', 'hey', 'hola'])
.addAnswer('👋 Welcome to our service!')
.addAnswer('How can I help you today? Type *register* to sign up or *help* for assistance.');
// Registration flow with state management
const registrationFlow = addKeyword(['register', 'signup', 'sign up'])
.addAnswer(
'To register, I need some information. What is your name?',
{ capture: true },
async (ctx, { flowDynamic, state, endFlow, fallBack }) => {
// Allow cancellation
if (ctx.body.toLowerCase() === 'cancel') {
return endFlow();
}
// Basic name validation
if (ctx.body.length < 2 || /^\\d+$/.test(ctx.body)) {
return fallBack(\`Please enter a valid name or type *cancel* to exit.\`);
}
// Save name to state
await state.update({ name: ctx.body });
// Confirm name and continue
await flowDynamic([\`Nice to meet you *\${ctx.body}*! Now, what's your email?\`]);
}
)
.addAnswer(
['Please enter your email address'],
{ capture: true },
async (ctx, { flowDynamic, state, endFlow, fallBack }) => {
// Allow cancellation
if (ctx.body.toLowerCase() === 'cancel') {
return endFlow();
}
// Validate email format
const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;
if (!emailRegex.test(ctx.body)) {
return fallBack(\`That doesn't look like a valid email. Please try again or type *cancel*.\`);
}
// Save email to state
await state.update({ email: ctx.body });
// Continue with flow
const userData = await state.get<any>('');
await flowDynamic([\`Thanks! Your information has been saved:\\nName: *\${userData.name}*\\nEmail: *\${userData.email}*\`]);
}
)
.addAnswer(
'Would you like to receive our newsletter? (yes/no)',
{ capture: true },
async (ctx, { flowDynamic, state, globalState, fallBack }) => {
// Check if response is valid
const response = ctx.body.toLowerCase();
if (response !== 'yes' && response !== 'no' &&
!response.includes('yes') && !response.includes('no')) {
return fallBack(\`Please answer with "yes" or "no".\`);
}
// Save preference to state
const wantsNewsletter = response.includes('yes');
await state.update({ newsletter: wantsNewsletter });
// Save to global state for all conversations
const userData = await state.get<any>('');
await globalState.update({
[\`user_\${ctx.from}\`]: {
name: userData.name,
email: userData.email,
newsletter: wantsNewsletter,
registeredAt: new Date().toISOString()
}
});
// Final confirmation
if (wantsNewsletter) {
await flowDynamic([\`Great! You've been added to our newsletter.\`]);
} else {
await flowDynamic([\`No problem! You won't receive our newsletter.\`]);
}
}
)
.addAnswer('Registration complete! Type *help* to see available commands.');
// Help flow
const helpFlow = addKeyword(['help', 'commands', 'options'])
.addAnswer('Here are the available commands:')
.addAnswer([
'• *hi* - Start a conversation',
'• *register* - Sign up for our service',
'• *help* - Show this help message',
'• *status* - Check your registration status'
]);
// Status flow
const statusFlow = addKeyword(['status', 'info'])
.addAction(
async (ctx, { flowDynamic, globalState, endFlow }) => {
// Get user data from global state
const allUsers = await globalState.get<any>('');
const userData = allUsers && allUsers[\`user_\${ctx.from}\`];
if (!userData) {
await flowDynamic([\`You're not registered yet. Type *register* to sign up.\`]);
return endFlow();
}
// Show user data
await flowDynamic([
\`*Registration Status*\\n\\nName: *\${userData.name}*\\nEmail: *\${userData.email}*\\nNewsletter: *\${userData.newsletter ? 'Yes' : 'No'}*\\nRegistered: *\${new Date(userData.registeredAt).toLocaleString()}*\`
]);
}
);
// Main function to initialize the bot and server
const main = async () => {
try {
console.log('Starting bot initialization...');
// Get configuration from environment variables
const wahaConfig = {
url: process.env.WAHA_API_URL || 'https://your-waha-api-url.com',
token: process.env.WAHA_API_KEY || 'your-api-key',
session: process.env.WAHA_SESSION || 'default'
};
console.log('Using Waha configuration:', {
url: wahaConfig.url,
session: wahaConfig.session,
// Don't log the token for security
});
// Create the provider
const adapterProvider = new WahaProvider(wahaConfig);
// Initialize the webhook for incoming messages
adapterProvider.initWebhook(app);
// Create the flow adapter with all flows
const adapterFlow = createFlow([
welcomeFlow,
registrationFlow,
helpFlow,
statusFlow
]);
// Create the database adapter with persistent storage
const adapterDB = new MemoryDB();
// Create the bot with all adapters
console.log('Creating bot with flows...');
const bot = await createBot({
flow: adapterFlow,
provider: adapterProvider,
database: adapterDB,
});
console.log('Bot created successfully, now ready to receive messages');
// Start the server
const PORT = process.env.PORT || 3000;
const server = app.listen(PORT, () => {
console.log(\`Server listening on port \${PORT}\`);
console.log('Webhook URL: /webhook/waha');
});
} catch (error) {
console.error('Error initializing bot:', error);
process.exit(1);
}
};
// Start the bot
main();`;
fs.writeFileSync(path.join(projectDir, 'src', 'app.ts'), appContent);
// Create .env.example
console.log('Creating .env.example...');
const envExample = `# WAHA API Configuration
WAHA_API_URL=https://your-waha-api-url.com
WAHA_API_KEY=your-api-key
WAHA_SESSION=default
# Server Configuration
PORT=3000
`;
fs.writeFileSync(path.join(projectDir, '.env.example'), envExample);
// Create .env
console.log('Creating .env...');
fs.writeFileSync(path.join(projectDir, '.env'), envExample);
// Create .gitignore
console.log('Creating .gitignore...');
const gitignore = `# Dependency directories
node_modules/
# Build output
dist/
# Environment variables
.env
# Logs
logs
*.log
npm-debug.log*
# IDE files
.idea/
.vscode/
*.swp
*.swo
# OS files
.DS_Store
Thumbs.db
`;
fs.writeFileSync(path.join(projectDir, '.gitignore'), gitignore);
// Create README.md
console.log('Creating README.md...');
const readme = `# ${projectName}
A WhatsApp chatbot built with BuilderBot and WAHA provider.
## Setup
1. Install dependencies:
\`\`\`
npm install
\`\`\`
2. Configure your environment variables:
- Copy \`.env.example\` to \`.env\`
- Update the values with your WAHA API details
3. Build the project:
\`\`\`
npm run build
\`\`\`
4. Start the bot:
\`\`\`
npm start
\`\`\`
## Development
Run in development mode with auto-reload:
\`\`\`
npm run dev
\`\`\`
## Features
- Welcome flow
- Registration flow with validation
- Newsletter subscription
- Help command
- Status check
## Webhook
The webhook endpoint is available at \`/webhook/waha\`. Configure your WAHA instance to send messages to this endpoint.
`;
fs.writeFileSync(path.join(projectDir, 'README.md'), readme);
console.log('\nProject created successfully!');
console.log(`\nNext steps:
1. cd ${projectName}
2. npm install
3. Update the .env file with your WAHA API details
4. npm run dev
`);
} else {
console.error(`Error: Unknown command '${command}'`);
console.log('Use --help to see available commands');
process.exit(1);
}