UNPKG

@smartuy/builderbot-provider-waha

Version:

WAHA (WhatsApp HTTP API) provider for BuilderBot desarrollado por SmartUY

399 lines (332 loc) 12.2 kB
#!/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); }