├── .env.example ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── src ├── controllers │ └── ConversationController.ts ├── index.ts ├── languages │ └── translations.ts ├── models │ └── ConversationFlow.ts ├── services │ ├── LanguageManager.ts │ └── WhatsAppService.ts └── utils │ └── helpers.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | # WhatsApp Customer Support Chatbot Configuration 2 | 3 | # Server Configuration 4 | PORT=3000 5 | NODE_ENV=development 6 | 7 | # WhatsApp Configuration 8 | WHATSAPP_SESSION_NAME=customer-support-bot 9 | 10 | # Business Configuration 11 | BUSINESS_NAME=Demo Store 12 | BUSINESS_TYPE=ecommerce 13 | SUPPORT_HOURS=9:00-18:00 14 | TIMEZONE=Africa/Lagos 15 | 16 | # Language Configuration 17 | DEFAULT_LANGUAGE=english 18 | SUPPORTED_LANGUAGES=english,pidgin,yoruba,hausa,igbo 19 | 20 | # Rate Limiting 21 | RATE_LIMIT_WINDOW_MS=900000 22 | RATE_LIMIT_MAX_REQUESTS=100 23 | 24 | # Webhook Configuration (Optional - for WhatsApp Business API) 25 | # WEBHOOK_VERIFY_TOKEN=your_webhook_verify_token 26 | # WHATSAPP_TOKEN=your_whatsapp_business_token 27 | # PHONE_NUMBER_ID=your_phone_number_id -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Environment variables 8 | .env 9 | .env.local 10 | .env.development.local 11 | .env.test.local 12 | .env.production.local 13 | 14 | # Build output 15 | dist/ 16 | build/ 17 | 18 | # Logs 19 | logs 20 | *.log 21 | 22 | # Runtime data 23 | pids 24 | *.pid 25 | *.seed 26 | *.pid.lock 27 | 28 | # Coverage directory used by tools like istanbul 29 | coverage/ 30 | *.lcov 31 | 32 | # nyc test coverage 33 | .nyc_output 34 | 35 | # IDE files 36 | .vscode/ 37 | .idea/ 38 | *.swp 39 | *.swo 40 | 41 | # OS generated files 42 | .DS_Store 43 | .DS_Store? 44 | ._* 45 | .Spotlight-V100 46 | .Trashes 47 | ehthumbs.db 48 | Thumbs.db 49 | 50 | # WhatsApp Web.js session data and cache 51 | .wwebjs_auth/ 52 | .wwebjs_cache/ 53 | 54 | # Puppeteer downloads 55 | .local-chromium/ 56 | 57 | # Temporary files 58 | *.tmp 59 | *.temp 60 | 61 | # Package lock files (choose one based on your preference) 62 | # package-lock.json 63 | # yarn.lock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WhatsApp Customer Support Chatbot 🤖 2 | 3 | A comprehensive multi-language WhatsApp customer support chatbot designed for local businesses in Nigeria. Supports English, Pidgin, Yoruba, Hausa, and Igbo languages with automated customer service for e-commerce, banking, telecom, and SME businesses. 4 | 5 | ## 🌟 Features 6 | 7 | - **Multi-language Support**: Automatic language detection and responses in 5 Nigerian languages 8 | - **Business-specific Flows**: Customized conversation flows for different business types 9 | - **Real-time WhatsApp Integration**: Direct integration with WhatsApp using whatsapp-web.js 10 | - **Smart Language Detection**: Automatically detects user's preferred language 11 | - **Session Management**: Maintains conversation context across interactions 12 | - **API Endpoints**: RESTful APIs for monitoring and external integrations 13 | - **Business Hours Support**: Automatic business hours checking 14 | - **Input Validation**: Robust validation for Nigerian phone numbers, emails, and order numbers 15 | - **Rate Limiting**: Built-in protection against spam and abuse 16 | - **Web Dashboard**: Monitor bot status and configuration 17 | 18 | ## 🏢 Supported Business Types 19 | 20 | ### E-commerce 21 | - Order tracking and status updates 22 | - Payment assistance and issue resolution 23 | - Return policy information 24 | - Product inquiries 25 | 26 | ### Banking 27 | - Account balance inquiries 28 | - Money transfer services 29 | - Bill payment assistance 30 | - Transaction support 31 | 32 | ### Telecom 33 | - Data plan information 34 | - Airtime purchase assistance 35 | - Network issue troubleshooting 36 | - Service activation 37 | 38 | ### SME (Small & Medium Enterprises) 39 | - General customer inquiries 40 | - Complaint handling 41 | - Feedback collection 42 | - Support ticket creation 43 | 44 | ## 🌍 Language Support 45 | 46 | | Language | Code | Sample Greeting | 47 | |----------|------|-----------------| 48 | | English | `en` | "Welcome to our business!" | 49 | | Pidgin | `pidgin` | "Welcome to our business! How I fit help you?" | 50 | | Yoruba | `yoruba` | "Kaabo si ile ise wa!" | 51 | | Hausa | `hausa` | "Barka da zuwa kasuwancinmu!" | 52 | | Igbo | `igbo` | "Nnọọ na azụmahịa anyị!" | 53 | 54 | ## 🚀 Quick Start 55 | 56 | ### Prerequisites 57 | 58 | - Node.js 18+ and npm 59 | - WhatsApp account for bot authentication 60 | - Chrome/Chromium browser (for WhatsApp Web.js) 61 | 62 | ### Installation 63 | 64 | 1. **Clone and setup** 65 | ```bash 66 | git clone 67 | cd CHATBOT 68 | npm install 69 | ``` 70 | 71 | 2. **Configure environment** 72 | ```bash 73 | cp .env.example .env 74 | # Edit .env with your business details 75 | ``` 76 | 77 | 3. **Build and run** 78 | ```bash 79 | npm run build 80 | npm start 81 | ``` 82 | 83 | 4. **For development** 84 | ```bash 85 | npm run dev 86 | ``` 87 | 88 | ### First Time Setup 89 | 90 | 1. **Start the bot**: Run `npm start` 91 | 2. **Scan QR Code**: A QR code will appear in the terminal 92 | 3. **Authenticate**: Scan the QR code with WhatsApp on your phone 93 | 4. **Bot Ready**: Once authenticated, the bot will be ready to receive messages 94 | 95 | ## 📋 Configuration 96 | 97 | ### Environment Variables 98 | 99 | ```env 100 | # Server Configuration 101 | PORT=3000 102 | NODE_ENV=development 103 | 104 | # Business Configuration 105 | BUSINESS_NAME=Your Business Name 106 | BUSINESS_TYPE=ecommerce # ecommerce, bank, telecom, sme 107 | SUPPORT_HOURS=9:00-18:00 108 | TIMEZONE=Africa/Lagos 109 | 110 | # Language Configuration 111 | DEFAULT_LANGUAGE=english 112 | SUPPORTED_LANGUAGES=english,pidgin,yoruba,hausa,igbo 113 | 114 | # WhatsApp Configuration 115 | WHATSAPP_SESSION_NAME=customer-support-bot 116 | 117 | # Optional: WhatsApp Business API 118 | WEBHOOK_VERIFY_TOKEN=your_webhook_verify_token 119 | WHATSAPP_TOKEN=your_whatsapp_business_token 120 | PHONE_NUMBER_ID=your_phone_number_id 121 | ``` 122 | 123 | ### Business Type Configuration 124 | 125 | Choose the appropriate business type to get customized conversation flows: 126 | 127 | - **ecommerce**: Order tracking, payment help, returns 128 | - **bank**: Account services, transfers, bill payments 129 | - **telecom**: Data plans, airtime, network support 130 | - **sme**: General support, complaints, feedback 131 | 132 | ## 🎯 Usage Examples 133 | 134 | ### Customer Interactions 135 | 136 | **English User:** 137 | ``` 138 | Customer: "Hi, I want to check my order status" 139 | Bot: "Welcome to [Business Name]! How can I help you today? 140 | 141 | Main Menu 142 | 1. Order Status & Tracking 143 | 2. Payment Help & Issues 144 | 3. General Inquiry 145 | 4. Change Language 146 | 5. Contact Support" 147 | ``` 148 | 149 | **Pidgin User:** 150 | ``` 151 | Customer: "Wetin dey happen with my order?" 152 | Bot: "Welcome to [Business Name]! How I fit help you today? 153 | 154 | Main Menu 155 | 1. Order Status & Tracking 156 | 2. Payment Help & Issues 157 | 3. General Question 158 | 4. Change Language 159 | 5. Contact Support" 160 | ``` 161 | 162 | ### API Usage 163 | 164 | **Check bot status:** 165 | ```bash 166 | curl http://localhost:3000/api/status 167 | ``` 168 | 169 | **Send message (for testing):** 170 | ```bash 171 | curl -X POST http://localhost:3000/api/send-message \ 172 | -H "Content-Type: application/json" \ 173 | -d '{"phoneNumber": "2348123456789", "message": "Test message"}' 174 | ``` 175 | 176 | ## 🛠️ Development 177 | 178 | ### Project Structure 179 | 180 | ``` 181 | src/ 182 | ├── index.ts # Main application entry point 183 | ├── controllers/ 184 | │ └── ConversationController.ts # Business logic & flows 185 | ├── services/ 186 | │ ├── WhatsAppService.ts # WhatsApp integration 187 | │ └── LanguageManager.ts # Language detection & translation 188 | ├── models/ 189 | │ └── ConversationFlow.ts # Data models & business flows 190 | ├── languages/ 191 | │ └── translations.ts # Multi-language translations 192 | └── utils/ 193 | └── helpers.ts # Utility functions 194 | ``` 195 | 196 | ### Adding New Languages 197 | 198 | 1. Add translations to `src/languages/translations.ts` 199 | 2. Update language detection in `src/services/LanguageManager.ts` 200 | 3. Add language choice mapping 201 | 202 | ### Adding New Business Flows 203 | 204 | 1. Define flow in `src/models/ConversationFlow.ts` 205 | 2. Update conversation handling in `src/controllers/ConversationController.ts` 206 | 3. Add business-specific logic 207 | 208 | ### Scripts 209 | 210 | ```bash 211 | npm run dev # Development with hot reload 212 | npm run build # Build TypeScript to JavaScript 213 | npm start # Start production server 214 | npm run test # Run tests (when implemented) 215 | ``` 216 | 217 | ## 📊 Monitoring 218 | 219 | ### Web Dashboard 220 | 221 | Visit `http://localhost:3000` to access the web dashboard showing: 222 | - Bot online/offline status 223 | - Configuration details 224 | - Supported features 225 | - API endpoints 226 | 227 | ### Health Check 228 | 229 | Monitor bot health at `http://localhost:3000/health` 230 | 231 | ### Logs 232 | 233 | The bot provides detailed logging for: 234 | - Message processing 235 | - Language detection 236 | - Conversation flows 237 | - Error handling 238 | - WhatsApp connection status 239 | 240 | ## 🔧 Troubleshooting 241 | 242 | ### Common Issues 243 | 244 | **QR Code not appearing:** 245 | - Ensure Chrome/Chromium is installed 246 | - Check terminal output for errors 247 | - Restart the application 248 | 249 | **WhatsApp disconnection:** 250 | - The bot automatically attempts to reconnect 251 | - Check internet connection 252 | - Re-scan QR code if needed 253 | 254 | **Message not processing:** 255 | - Check conversation state in logs 256 | - Verify language detection is working 257 | - Ensure proper environment configuration 258 | 259 | ### Performance Tips 260 | 261 | - Run session cleanup periodically 262 | - Monitor memory usage for large conversation volumes 263 | - Consider database integration for production use 264 | - Implement message queuing for high volume 265 | 266 | ## 🤝 Contributing 267 | 268 | 1. Fork the repository 269 | 2. Create a feature branch 270 | 3. Add tests for new functionality 271 | 4. Submit a pull request 272 | 273 | ## 📄 License 274 | 275 | ISC License - see LICENSE file for details 276 | 277 | ## 🆘 Support 278 | 279 | For support and questions: 280 | - Check the troubleshooting section 281 | - Review logs for error messages 282 | - Open an issue on GitHub 283 | 284 | --- 285 | 286 | **Built for Nigerian businesses with ❤️** -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "whatsapp-customer-support-chatbot", 3 | "version": "1.0.0", 4 | "description": "Multi-language WhatsApp customer support chatbot for local businesses in Nigeria", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "start": "node dist/index.js", 8 | "dev": "nodemon src/index.ts", 9 | "build": "tsc", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "keywords": [ 13 | "whatsapp", 14 | "chatbot", 15 | "customer-support", 16 | "nigeria", 17 | "multi-language" 18 | ], 19 | "author": "", 20 | "license": "ISC", 21 | "dependencies": { 22 | "cors": "^2.8.5", 23 | "dotenv": "^16.3.1", 24 | "express": "^4.18.2", 25 | "express-rate-limit": "^7.1.5", 26 | "helmet": "^7.1.0", 27 | "natural": "^6.12.0", 28 | "node-nlp": "^4.27.0", 29 | "qrcode-terminal": "^0.12.0", 30 | "whatsapp-web.js": "^1.23.0" 31 | }, 32 | "devDependencies": { 33 | "@types/cors": "^2.8.18", 34 | "@types/express": "^4.17.21", 35 | "@types/node": "^20.10.4", 36 | "@types/qrcode-terminal": "^0.12.2", 37 | "nodemon": "^3.0.2", 38 | "ts-node": "^10.9.2", 39 | "typescript": "^5.3.3" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/controllers/ConversationController.ts: -------------------------------------------------------------------------------- 1 | import { LanguageManager } from '../services/LanguageManager'; 2 | import { ConversationState, BusinessFlow, businessFlows } from '../models/ConversationFlow'; 3 | 4 | export class ConversationController { 5 | private languageManager: LanguageManager; 6 | private conversations: Map = new Map(); 7 | private businessName: string; 8 | private businessType: string; 9 | private supportHours: string; 10 | 11 | constructor() { 12 | this.languageManager = new LanguageManager(); 13 | this.businessName = process.env.BUSINESS_NAME || 'Your Business'; 14 | this.businessType = process.env.BUSINESS_TYPE || 'ecommerce'; 15 | this.supportHours = process.env.SUPPORT_HOURS || '9:00-18:00'; 16 | } 17 | 18 | public async processMessage(phoneNumber: string, message: string): Promise { 19 | const conversation = this.getOrCreateConversation(phoneNumber); 20 | const userInput = message.toLowerCase().trim(); 21 | 22 | // Handle global commands 23 | if (this.isGlobalCommand(userInput)) { 24 | return this.handleGlobalCommand(conversation, userInput); 25 | } 26 | 27 | // Handle language selection 28 | if (conversation.currentFlow === 'language_selection') { 29 | return this.handleLanguageSelection(conversation, userInput); 30 | } 31 | 32 | // Handle business flow 33 | if (conversation.currentFlow && conversation.currentFlow !== 'main_menu') { 34 | return this.handleBusinessFlow(conversation, userInput); 35 | } 36 | 37 | // Handle main menu selection 38 | if (this.isMainMenuOption(userInput)) { 39 | return this.handleMainMenuSelection(conversation, userInput); 40 | } 41 | 42 | // First-time user or unrecognized input 43 | if (!conversation.currentFlow || userInput === 'start' || userInput === 'hi' || userInput === 'hello') { 44 | return this.handleWelcome(conversation, message); 45 | } 46 | 47 | // Default fallback 48 | return this.handleUnknownInput(conversation); 49 | } 50 | 51 | private getOrCreateConversation(phoneNumber: string): ConversationState { 52 | if (!this.conversations.has(phoneNumber)) { 53 | const detectedLanguage = this.languageManager.detectLanguage('hello'); 54 | this.conversations.set(phoneNumber, { 55 | phoneNumber, 56 | language: detectedLanguage, 57 | businessType: this.businessType as any, 58 | currentFlow: '', 59 | stepInFlow: 0, 60 | userData: {}, 61 | lastActivity: new Date() 62 | }); 63 | } 64 | 65 | const conversation = this.conversations.get(phoneNumber)!; 66 | conversation.lastActivity = new Date(); 67 | return conversation; 68 | } 69 | 70 | private isGlobalCommand(input: string): boolean { 71 | const commands = ['menu', 'help', 'start', 'language', 'support', 'contact']; 72 | return commands.some(cmd => input.includes(cmd)); 73 | } 74 | 75 | private handleGlobalCommand(conversation: ConversationState, input: string): string { 76 | if (input.includes('menu')) { 77 | conversation.currentFlow = 'main_menu'; 78 | conversation.stepInFlow = 0; 79 | return this.getMainMenu(conversation); 80 | } 81 | 82 | if (input.includes('help')) { 83 | return this.languageManager.getMessage(conversation.language, 'help'); 84 | } 85 | 86 | if (input.includes('language')) { 87 | conversation.currentFlow = 'language_selection'; 88 | return this.languageManager.getMessage(conversation.language, 'choose_language'); 89 | } 90 | 91 | if (input.includes('support') || input.includes('contact')) { 92 | return this.languageManager.getMessage( 93 | conversation.language, 94 | 'contact_support', 95 | { supportHours: this.supportHours } 96 | ); 97 | } 98 | 99 | return this.handleWelcome(conversation, input); 100 | } 101 | 102 | private handleLanguageSelection(conversation: ConversationState, input: string): string { 103 | const selectedLanguage = this.languageManager.getLanguageFromChoice(input); 104 | 105 | if (selectedLanguage) { 106 | conversation.language = selectedLanguage; 107 | conversation.currentFlow = 'main_menu'; 108 | 109 | const languageChangedMsg = this.languageManager.getMessage( 110 | conversation.language, 111 | 'language_changed' 112 | ); 113 | 114 | const menuMsg = this.getMainMenu(conversation); 115 | return `${languageChangedMsg}\n\n${menuMsg}`; 116 | } 117 | 118 | return this.languageManager.getMessage( 119 | conversation.language, 120 | 'invalid_option' 121 | ) + '\n\n' + this.languageManager.getMessage(conversation.language, 'choose_language'); 122 | } 123 | 124 | private handleWelcome(conversation: ConversationState, message: string): string { 125 | // Detect language from user's first message 126 | const detectedLanguage = this.languageManager.detectLanguage(message); 127 | conversation.language = detectedLanguage; 128 | conversation.currentFlow = 'main_menu'; 129 | 130 | const welcomeMsg = this.languageManager.getMessage( 131 | conversation.language, 132 | 'welcome', 133 | { businessName: this.businessName } 134 | ); 135 | 136 | const menuMsg = this.getMainMenu(conversation); 137 | return `${welcomeMsg}\n\n${menuMsg}`; 138 | } 139 | 140 | private getMainMenu(conversation: ConversationState): string { 141 | const businessType = conversation.businessType; 142 | let menuOptions = ''; 143 | 144 | // Build menu based on business type 145 | if (businessType === 'ecommerce') { 146 | menuOptions = `1. ${this.languageManager.getMessage(conversation.language, 'order_status')} 147 | 2. ${this.languageManager.getMessage(conversation.language, 'payment_help')} 148 | 3. ${this.languageManager.getMessage(conversation.language, 'general_inquiry')} 149 | 4. ${this.languageManager.getMessage(conversation.language, 'choose_language')} 150 | 5. ${this.languageManager.getMessage(conversation.language, 'contact_support')}`; 151 | } else if (businessType === 'bank') { 152 | menuOptions = `1. ${this.languageManager.getMessage(conversation.language, 'account_balance')} 153 | 2. ${this.languageManager.getMessage(conversation.language, 'transfer_money')} 154 | 3. ${this.languageManager.getMessage(conversation.language, 'bill_payment')} 155 | 4. ${this.languageManager.getMessage(conversation.language, 'choose_language')} 156 | 5. ${this.languageManager.getMessage(conversation.language, 'contact_support')}`; 157 | } else if (businessType === 'telecom') { 158 | menuOptions = `1. ${this.languageManager.getMessage(conversation.language, 'data_plans')} 159 | 2. ${this.languageManager.getMessage(conversation.language, 'airtime_purchase')} 160 | 3. ${this.languageManager.getMessage(conversation.language, 'network_issues')} 161 | 4. ${this.languageManager.getMessage(conversation.language, 'choose_language')} 162 | 5. ${this.languageManager.getMessage(conversation.language, 'contact_support')}`; 163 | } else { 164 | menuOptions = `1. ${this.languageManager.getMessage(conversation.language, 'general_inquiry')} 165 | 2. ${this.languageManager.getMessage(conversation.language, 'payment_help')} 166 | 3. ${this.languageManager.getMessage(conversation.language, 'complaint')} 167 | 4. ${this.languageManager.getMessage(conversation.language, 'choose_language')} 168 | 5. ${this.languageManager.getMessage(conversation.language, 'contact_support')}`; 169 | } 170 | 171 | return `${this.languageManager.getMessage(conversation.language, 'main_menu')}\n\n${menuOptions}`; 172 | } 173 | 174 | private isMainMenuOption(input: string): boolean { 175 | return /^[1-5]$/.test(input); 176 | } 177 | 178 | private handleMainMenuSelection(conversation: ConversationState, input: string): string { 179 | const option = parseInt(input); 180 | const businessType = conversation.businessType; 181 | 182 | switch (option) { 183 | case 1: 184 | if (businessType === 'ecommerce') { 185 | return this.startFlow(conversation, 'order_tracking'); 186 | } else if (businessType === 'bank') { 187 | return this.startFlow(conversation, 'account_services'); 188 | } else if (businessType === 'telecom') { 189 | return this.startFlow(conversation, 'telecom_services'); 190 | } else { 191 | return this.startFlow(conversation, 'general_support'); 192 | } 193 | case 2: 194 | return this.startFlow(conversation, 'payment_help'); 195 | case 3: 196 | if (businessType === 'telecom') { 197 | return this.handleNetworkIssues(conversation); 198 | } else { 199 | return this.startFlow(conversation, 'general_support'); 200 | } 201 | case 4: 202 | conversation.currentFlow = 'language_selection'; 203 | return this.languageManager.getMessage(conversation.language, 'choose_language'); 204 | case 5: 205 | return this.languageManager.getMessage( 206 | conversation.language, 207 | 'contact_support', 208 | { supportHours: this.supportHours } 209 | ); 210 | default: 211 | return this.languageManager.getMessage(conversation.language, 'invalid_option'); 212 | } 213 | } 214 | 215 | private startFlow(conversation: ConversationState, flowId: string): string { 216 | const flow = businessFlows.find(f => f.id === flowId); 217 | if (!flow) { 218 | return this.languageManager.getMessage(conversation.language, 'invalid_option'); 219 | } 220 | 221 | conversation.currentFlow = flowId; 222 | conversation.stepInFlow = 0; 223 | 224 | const firstStep = flow.steps[0]; 225 | if (firstStep.message in this.languageManager.getTranslation(conversation.language)) { 226 | return this.languageManager.getMessage(conversation.language, firstStep.message as any); 227 | } 228 | 229 | return firstStep.message; 230 | } 231 | 232 | private handleBusinessFlow(conversation: ConversationState, input: string): string { 233 | const flow = businessFlows.find(f => f.id === conversation.currentFlow); 234 | if (!flow) { 235 | conversation.currentFlow = 'main_menu'; 236 | return this.getMainMenu(conversation); 237 | } 238 | 239 | const currentStep = flow.steps[conversation.stepInFlow]; 240 | if (!currentStep) { 241 | conversation.currentFlow = 'main_menu'; 242 | return this.getMainMenu(conversation); 243 | } 244 | 245 | // Validate input based on expected type 246 | if (!this.validateInput(input, currentStep.expectedInputType, currentStep.validation)) { 247 | return `Invalid input. ${currentStep.message}`; 248 | } 249 | 250 | // Store user data 251 | conversation.userData[currentStep.id] = input; 252 | 253 | // Move to next step or complete flow 254 | if (currentStep.nextStep) { 255 | const nextStepIndex = flow.steps.findIndex(s => s.id === currentStep.nextStep); 256 | if (nextStepIndex !== -1) { 257 | conversation.stepInFlow = nextStepIndex; 258 | return this.processBusinessAction(conversation, currentStep, flow.steps[nextStepIndex]); 259 | } 260 | } 261 | 262 | // Flow completed 263 | conversation.currentFlow = 'main_menu'; 264 | return this.processBusinessAction(conversation, currentStep) + '\n\n' + this.getMainMenu(conversation); 265 | } 266 | 267 | private validateInput(input: string, expectedType: string, validation?: RegExp): boolean { 268 | switch (expectedType) { 269 | case 'choice': 270 | return /^[1-5]$/.test(input); 271 | case 'number': 272 | return /^\d+$/.test(input); 273 | case 'order_number': 274 | return validation ? validation.test(input) : /^[A-Z0-9]{6,20}$/.test(input); 275 | case 'phone': 276 | return /^(\+234|0)[0-9]{10}$/.test(input); 277 | case 'email': 278 | return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input); 279 | default: 280 | return true; 281 | } 282 | } 283 | 284 | private processBusinessAction(conversation: ConversationState, currentStep: any, nextStep?: any): string { 285 | // Simulate business logic based on action 286 | if (currentStep.action === 'display_order_status') { 287 | const orderNumber = conversation.userData.request_order_number; 288 | return `Order ${orderNumber}: Your order is being processed and will be delivered within 2-3 business days.`; 289 | } 290 | 291 | if (currentStep.action === 'handle_payment_request') { 292 | return 'Your payment issue has been logged. Our team will contact you within 24 hours.'; 293 | } 294 | 295 | if (nextStep) { 296 | if (nextStep.message in this.languageManager.getTranslation(conversation.language)) { 297 | return this.languageManager.getMessage(conversation.language, nextStep.message as any); 298 | } 299 | return nextStep.message; 300 | } 301 | 302 | return this.languageManager.getMessage(conversation.language, 'thank_you'); 303 | } 304 | 305 | private handleNetworkIssues(conversation: ConversationState): string { 306 | return `${this.languageManager.getMessage(conversation.language, 'network_issues')} 307 | 308 | Please try the following: 309 | 1. Restart your device 310 | 2. Check if you're in a coverage area 311 | 3. Contact support if issue persists 312 | 313 | ${this.languageManager.getMessage(conversation.language, 'contact_support', { supportHours: this.supportHours })}`; 314 | } 315 | 316 | private handleUnknownInput(conversation: ConversationState): string { 317 | return this.languageManager.getMessage(conversation.language, 'invalid_option') + 318 | '\n\n' + this.getMainMenu(conversation); 319 | } 320 | 321 | // Clean up old conversations (call periodically) 322 | public cleanupOldConversations(maxAgeHours: number = 24): void { 323 | const cutoffTime = new Date(Date.now() - maxAgeHours * 60 * 60 * 1000); 324 | 325 | for (const [phoneNumber, conversation] of this.conversations.entries()) { 326 | if (conversation.lastActivity < cutoffTime) { 327 | this.conversations.delete(phoneNumber); 328 | } 329 | } 330 | } 331 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | import express, { Request, Response, NextFunction } from 'express'; 3 | import cors from 'cors'; 4 | import helmet from 'helmet'; 5 | import rateLimit from 'express-rate-limit'; 6 | import { WhatsAppService } from './services/WhatsAppService'; 7 | 8 | // Load environment variables 9 | dotenv.config(); 10 | 11 | class CustomerSupportBot { 12 | private app: express.Application; 13 | private whatsappService: WhatsAppService; 14 | private port: number; 15 | 16 | constructor() { 17 | this.app = express(); 18 | this.port = parseInt(process.env.PORT || '3000'); 19 | this.whatsappService = new WhatsAppService(); 20 | 21 | this.setupMiddleware(); 22 | this.setupRoutes(); 23 | this.setupErrorHandling(); 24 | } 25 | 26 | private setupMiddleware(): void { 27 | // Security middleware 28 | this.app.use(helmet()); 29 | this.app.use(cors()); 30 | 31 | // Rate limiting 32 | const limiter = rateLimit({ 33 | windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '900000'), // 15 minutes 34 | max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '100'), 35 | message: 'Too many requests from this IP, please try again later.' 36 | }); 37 | this.app.use('/api/', limiter); 38 | 39 | // Body parsing 40 | this.app.use(express.json()); 41 | this.app.use(express.urlencoded({ extended: true })); 42 | } 43 | 44 | private setupRoutes(): void { 45 | // Health check endpoint 46 | this.app.get('/health', (req: Request, res: Response) => { 47 | res.json({ 48 | status: 'OK', 49 | timestamp: new Date().toISOString(), 50 | whatsappReady: this.whatsappService.isClientReady(), 51 | businessName: process.env.BUSINESS_NAME || 'Customer Support Bot', 52 | supportedLanguages: ['English', 'Pidgin', 'Yoruba', 'Hausa', 'Igbo'] 53 | }); 54 | }); 55 | 56 | // Bot status endpoint 57 | this.app.get('/api/status', (req: Request, res: Response) => { 58 | res.json({ 59 | botStatus: this.whatsappService.isClientReady() ? 'online' : 'offline', 60 | businessType: process.env.BUSINESS_TYPE || 'ecommerce', 61 | supportHours: process.env.SUPPORT_HOURS || '9:00-18:00', 62 | uptime: process.uptime(), 63 | version: '1.0.0' 64 | }); 65 | }); 66 | 67 | // Webhook endpoint for WhatsApp Business API (optional) 68 | this.app.get('/api/webhook', (req: Request, res: Response) => { 69 | const verifyToken = process.env.WEBHOOK_VERIFY_TOKEN; 70 | const mode = req.query['hub.mode']; 71 | const token = req.query['hub.verify_token']; 72 | const challenge = req.query['hub.challenge']; 73 | 74 | if (mode && token) { 75 | if (mode === 'subscribe' && token === verifyToken) { 76 | console.log('Webhook verified successfully!'); 77 | res.status(200).send(challenge); 78 | } else { 79 | res.status(403).send('Forbidden'); 80 | } 81 | } else { 82 | res.status(400).send('Bad Request'); 83 | } 84 | }); 85 | 86 | // Webhook POST endpoint for WhatsApp Business API 87 | this.app.post('/api/webhook', async (req: Request, res: Response) => { 88 | try { 89 | // Handle WhatsApp Business API webhooks here 90 | console.log('Received webhook:', JSON.stringify(req.body, null, 2)); 91 | res.status(200).send('OK'); 92 | } catch (error) { 93 | console.error('Webhook error:', error); 94 | res.status(500).send('Internal Server Error'); 95 | } 96 | }); 97 | 98 | // Send message endpoint (for testing or external integrations) 99 | this.app.post('/api/send-message', async (req: Request, res: Response) => { 100 | try { 101 | const { phoneNumber, message } = req.body; 102 | 103 | if (!phoneNumber || !message) { 104 | return res.status(400).json({ error: 'Phone number and message are required' }); 105 | } 106 | 107 | if (!this.whatsappService.isClientReady()) { 108 | return res.status(503).json({ error: 'WhatsApp service not ready' }); 109 | } 110 | 111 | await this.whatsappService.sendMessage(phoneNumber, message); 112 | res.json({ success: true, message: 'Message sent successfully' }); 113 | } catch (error) { 114 | console.error('Send message error:', error); 115 | res.status(500).json({ error: 'Failed to send message' }); 116 | } 117 | }); 118 | 119 | // Dashboard endpoint 120 | this.app.get('/', (req: Request, res: Response) => { 121 | res.send(` 122 | 123 | 124 | 125 | WhatsApp Customer Support Bot 126 | 127 | 128 | 138 | 139 | 140 |
141 |

🤖 WhatsApp Customer Support Bot

142 |
143 | Status: ${this.whatsappService.isClientReady() ? '🟢 Online' : '🔴 Offline'} 144 |
145 | 146 |

Features

147 |
✅ Multi-language support (English, Pidgin, Yoruba, Hausa, Igbo)
148 |
✅ Business-specific flows (E-commerce, Banking, Telecom, SME)
149 |
✅ Automated customer service
150 |
✅ Real-time WhatsApp integration
151 | 152 |

Configuration

153 |

Business Name: ${process.env.BUSINESS_NAME || 'Your Business'}

154 |

Business Type: ${process.env.BUSINESS_TYPE || 'ecommerce'}

155 |

Support Hours: ${process.env.SUPPORT_HOURS || '9:00-18:00'}

156 | 157 |

API Endpoints

158 |
GET /health - Health check
159 |
GET /api/status - Bot status
160 |
POST /api/send-message - Send message
161 |
GET /api/webhook - Webhook verification
162 | 163 |

Bot is running on port ${this.port}

164 |
165 | 166 | 167 | `); 168 | }); 169 | } 170 | 171 | private setupErrorHandling(): void { 172 | // 404 handler 173 | this.app.use('*', (req: Request, res: Response) => { 174 | res.status(404).json({ error: 'Endpoint not found' }); 175 | }); 176 | 177 | // Global error handler 178 | this.app.use((err: any, req: Request, res: Response, next: NextFunction) => { 179 | console.error('Unhandled error:', err); 180 | res.status(500).json({ error: 'Internal server error' }); 181 | }); 182 | } 183 | 184 | public async start(): Promise { 185 | try { 186 | console.log('🚀 Starting WhatsApp Customer Support Bot...'); 187 | 188 | // Start WhatsApp service 189 | await this.whatsappService.initialize(); 190 | 191 | // Start web server 192 | this.app.listen(this.port, () => { 193 | console.log(`🌐 Web server running on http://localhost:${this.port}`); 194 | console.log(`📱 WhatsApp Bot Status: ${this.whatsappService.isClientReady() ? 'Ready' : 'Initializing...'}`); 195 | console.log(`🏢 Business Type: ${process.env.BUSINESS_TYPE || 'ecommerce'}`); 196 | console.log(`🌍 Supported Languages: English, Pidgin, Yoruba, Hausa, Igbo`); 197 | }); 198 | 199 | // Setup cleanup on exit 200 | process.on('SIGINT', this.shutdown.bind(this)); 201 | process.on('SIGTERM', this.shutdown.bind(this)); 202 | 203 | } catch (error) { 204 | console.error('❌ Failed to start bot:', error); 205 | process.exit(1); 206 | } 207 | } 208 | 209 | private async shutdown(): Promise { 210 | console.log('🛑 Shutting down gracefully...'); 211 | 212 | try { 213 | await this.whatsappService.destroy(); 214 | console.log('✅ WhatsApp service stopped'); 215 | process.exit(0); 216 | } catch (error) { 217 | console.error('❌ Error during shutdown:', error); 218 | process.exit(1); 219 | } 220 | } 221 | } 222 | 223 | // Start the bot 224 | const bot = new CustomerSupportBot(); 225 | bot.start().catch(console.error); -------------------------------------------------------------------------------- /src/languages/translations.ts: -------------------------------------------------------------------------------- 1 | export interface Translation { 2 | welcome: string; 3 | menu: string; 4 | help: string; 5 | contact_support: string; 6 | business_hours: string; 7 | invalid_option: string; 8 | thank_you: string; 9 | goodbye: string; 10 | language_changed: string; 11 | choose_language: string; 12 | main_menu: string; 13 | order_status: string; 14 | track_order: string; 15 | return_policy: string; 16 | payment_help: string; 17 | account_balance: string; 18 | transfer_money: string; 19 | bill_payment: string; 20 | network_issues: string; 21 | data_plans: string; 22 | airtime_purchase: string; 23 | general_inquiry: string; 24 | complaint: string; 25 | feedback: string; 26 | } 27 | 28 | export const translations: Record = { 29 | english: { 30 | welcome: "Welcome to {businessName}! How can I help you today?", 31 | menu: "Please choose from the following options:\n1. Order Status\n2. Payment Help\n3. Contact Support\n4. Change Language\n5. Help", 32 | help: "I'm here to assist you with:\n• Order tracking\n• Payment issues\n• General inquiries\n• Account support\n\nType 'menu' to see all options.", 33 | contact_support: "You can reach our support team:\n📞 Phone: +234-XXX-XXXX\n📧 Email: support@business.com\n🕒 Hours: {supportHours}", 34 | business_hours: "Our business hours are {supportHours}, West Africa Time.", 35 | invalid_option: "I don't understand that option. Please type 'menu' to see available options.", 36 | thank_you: "Thank you for contacting us!", 37 | goodbye: "Goodbye! Have a great day!", 38 | language_changed: "Language changed to English.", 39 | choose_language: "Choose your language:\n1. English\n2. Pidgin\n3. Yoruba\n4. Hausa\n5. Igbo", 40 | main_menu: "Main Menu", 41 | order_status: "Order Status & Tracking", 42 | track_order: "Please provide your order number to track your order.", 43 | return_policy: "Our return policy allows returns within 30 days of purchase. Items must be in original condition.", 44 | payment_help: "Payment Help & Issues", 45 | account_balance: "Account Balance Inquiry", 46 | transfer_money: "Money Transfer Services", 47 | bill_payment: "Bill Payment Services", 48 | network_issues: "Network & Connectivity Issues", 49 | data_plans: "Data Plans & Packages", 50 | airtime_purchase: "Airtime Purchase", 51 | general_inquiry: "General Inquiry", 52 | complaint: "File a Complaint", 53 | feedback: "Provide Feedback" 54 | }, 55 | 56 | pidgin: { 57 | welcome: "Welcome to {businessName}! How I fit help you today?", 58 | menu: "Please choose from dis options:\n1. Order Status\n2. Payment Help\n3. Contact Support\n4. Change Language\n5. Help", 59 | help: "I dey here to help you with:\n• Order tracking\n• Payment wahala\n• General questions\n• Account support\n\nType 'menu' to see all options.", 60 | contact_support: "You fit reach our support team:\n📞 Phone: +234-XXX-XXXX\n📧 Email: support@business.com\n🕒 Hours: {supportHours}", 61 | business_hours: "Our business hours na {supportHours}, West Africa Time.", 62 | invalid_option: "I no understand that option. Please type 'menu' to see available options.", 63 | thank_you: "Thank you for contacting us!", 64 | goodbye: "Bye bye! Make your day dey sweet!", 65 | language_changed: "Language don change to Pidgin.", 66 | choose_language: "Choose your language:\n1. English\n2. Pidgin\n3. Yoruba\n4. Hausa\n5. Igbo", 67 | main_menu: "Main Menu", 68 | order_status: "Order Status & Tracking", 69 | track_order: "Please give us your order number make we track your order.", 70 | return_policy: "Our return policy allow return within 30 days after you buy. Items must dey original condition.", 71 | payment_help: "Payment Help & Issues", 72 | account_balance: "Account Balance Check", 73 | transfer_money: "Money Transfer Services", 74 | bill_payment: "Bill Payment Services", 75 | network_issues: "Network & Connection Wahala", 76 | data_plans: "Data Plans & Packages", 77 | airtime_purchase: "Airtime Purchase", 78 | general_inquiry: "General Question", 79 | complaint: "Make Complaint", 80 | feedback: "Give Feedback" 81 | }, 82 | 83 | yoruba: { 84 | welcome: "Kaabo si {businessName}! Bawo ni mo se le ran e lowo loni?", 85 | menu: "Jowo yan lati inu awon yiyan wonyi:\n1. Ipo Order\n2. Iranlowo Isanwo\n3. Kan si Atilese\n4. Yi Ede Pada\n5. Iranlowo", 86 | help: "Mo wa nibi lati ran yin lowo pelu:\n• Itopase order\n• Awon isoro isanwo\n• Awon ibeere gbogbogbo\n• Atilese account\n\nTe 'menu' lati ri gbogbo awon yiyan.", 87 | contact_support: "E le kan si atilese wa:\n📞 Phone: +234-XXX-XXXX\n📧 Email: support@business.com\n🕒 Wakati: {supportHours}", 88 | business_hours: "Awon wakati ise wa ni {supportHours}, West Africa Time.", 89 | invalid_option: "Mi o ni oye yiyan yen. Jowo te 'menu' lati ri awon yiyan to wa.", 90 | thank_you: "E se fun pipe e kan si wa!", 91 | goodbye: "O dabo! Ki ojo yin dun!", 92 | language_changed: "Ede ti yi pada si Yoruba.", 93 | choose_language: "Yan ede yin:\n1. English\n2. Pidgin\n3. Yoruba\n4. Hausa\n5. Igbo", 94 | main_menu: "Menu Akoko", 95 | order_status: "Ipo Order & Itopase", 96 | track_order: "Jowo fun wa ni nọmba order yin ki a le tọpa.", 97 | return_policy: "Ilana ipadabọ wa gba ipadabọ laarin ọjọ 30 lẹhin rira. Awọn nkan gbọdọ wa ni ipo atilẹba.", 98 | payment_help: "Iranlowo Isanwo & Awon Isoro", 99 | account_balance: "Ibeere Iwọntunwọnsi Account", 100 | transfer_money: "Awon Iṣẹ Gbigbe Owo", 101 | bill_payment: "Awon Iṣẹ Sisanwo Bill", 102 | network_issues: "Network & Awon Isoro Asopọ", 103 | data_plans: "Awon Eto Data & Packages", 104 | airtime_purchase: "Rira Airtime", 105 | general_inquiry: "Ibeere Gbogbogbo", 106 | complaint: "Fi Aropin Sile", 107 | feedback: "Fun ni Ifọrọranşọ" 108 | }, 109 | 110 | hausa: { 111 | welcome: "Barka da zuwa {businessName}! Yaya zan iya taimaka muku yau?", 112 | menu: "Da fatan za ku zaɓa daga waɗannan zaɓuɓɓuka:\n1. Matsayin Order\n2. Taimakon Biyan Kuɗi\n3. Tuntuɓar Tallafi\n4. Canja Harshe\n5. Taimako", 113 | help: "Ina nan don in taimake ku da:\n• Bin diddigin order\n• Matsalolin biyan kuɗi\n• Tambayoyi na gabaɗaya\n• Tallafin account\n\nRubuta 'menu' don ganin dukan zaɓuɓɓuka.", 114 | contact_support: "Kuna iya tuntuɓar ƙungiyar tallafi:\n📞 Phone: +234-XXX-XXXX\n📧 Email: support@business.com\n🕒 Lokutan: {supportHours}", 115 | business_hours: "Lokutan kasuwancinmu {supportHours}, West Africa Time.", 116 | invalid_option: "Ban fahimci wannan zaɓi ba. Da fatan za ku rubuta 'menu' don ganin zaɓuɓɓukan da ake da su.", 117 | thank_you: "Na gode da tuntuɓar mu!", 118 | goodbye: "Sai anjima! Ku yi kyakkyawan rana!", 119 | language_changed: "Harshe ya canza zuwa Hausa.", 120 | choose_language: "Zaɓi harshenku:\n1. English\n2. Pidgin\n3. Yoruba\n4. Hausa\n5. Igbo", 121 | main_menu: "Babban Menu", 122 | order_status: "Matsayin Order & Bin diddigin", 123 | track_order: "Da fatan za ku bayar da lambar order ɗinku don mu bi diddiginsa.", 124 | return_policy: "Manufarmu ta dawo da kaya tana ba da damar dawowa cikin kwanaki 30 bayan siye. Kayan dole su kasance a yanayin asali.", 125 | payment_help: "Taimakon Biyan Kuɗi & Matsaloli", 126 | account_balance: "Tambayar Ma'auni na Account", 127 | transfer_money: "Sabis na Canja Kuɗi", 128 | bill_payment: "Sabis na Biyan Bill", 129 | network_issues: "Network & Matsalolin Haɗi", 130 | data_plans: "Tsare-tsaren Data & Packages", 131 | airtime_purchase: "Siyan Airtime", 132 | general_inquiry: "Tambaya ta Gabaɗaya", 133 | complaint: "Shigar da Korafi", 134 | feedback: "Bayar da Ra'ayi" 135 | }, 136 | 137 | igbo: { 138 | welcome: "Nnọọ na {businessName}! Kedu ka m ga-esi nyere gị aka taa?", 139 | menu: "Biko họrọ site na nhọrọ ndị a:\n1. Ọnọdụ Order\n2. Enyemaka Ịkwụ Ụgwọ\n3. Kpọtụrụ Nkwado\n4. Gbanwee Asụsụ\n5. Enyemaka", 140 | help: "Anọ m ebe a iji nyere gị aka na:\n• Nsuso order\n• Nsogbu ịkwụ ụgwọ\n• Ajụjụ izugbe\n• Nkwado account\n\nDee 'menu' ka ịhụ nhọrọ niile.", 141 | contact_support: "Ị nwere ike iru ndị otu nkwado anyị:\n📞 Phone: +234-XXX-XXXX\n📧 Email: support@business.com\n🕒 Oge: {supportHours}", 142 | business_hours: "Oge azụmahịa anyị bụ {supportHours}, West Africa Time.", 143 | invalid_option: "Aghọtaghị m nhọrọ ahụ. Biko dee 'menu' ka ịhụ nhọrọ dị.", 144 | thank_you: "Daalụ maka ịkpọtụrụ anyị!", 145 | goodbye: "Ndewo! Nwee ọmarịcha ụbọchị!", 146 | language_changed: "Agbanweela asụsụ ka ọ bụrụ Igbo.", 147 | choose_language: "Họrọ asụsụ gị:\n1. English\n2. Pidgin\n3. Yoruba\n4. Hausa\n5. Igbo", 148 | main_menu: "Menu Isi", 149 | order_status: "Ọnọdụ Order & Nsuso", 150 | track_order: "Biko nye anyị nọmba order gị ka anyị nwee ike ịsoro ya.", 151 | return_policy: "Usoro nweghachi anyị na-enye ohere nweghachi n'ime ụbọchị 30 mgbe ịzụrụ. Ihe ndị ahụ kwesịrị ịnọ n'ọnọdụ mbụ.", 152 | payment_help: "Enyemaka Ịkwụ Ụgwọ & Nsogbu", 153 | account_balance: "Ajụjụ Nguzozi Account", 154 | transfer_money: "Ọrụ Ịnyefe Ego", 155 | bill_payment: "Ọrụ Ịkwụ Bill", 156 | network_issues: "Network & Nsogbu Njikọ", 157 | data_plans: "Atụmatụ Data & Packages", 158 | airtime_purchase: "Ịzụ Airtime", 159 | general_inquiry: "Ajụjụ Izugbe", 160 | complaint: "Tinye Mkpesa", 161 | feedback: "Nye Nzaghachi" 162 | } 163 | }; -------------------------------------------------------------------------------- /src/models/ConversationFlow.ts: -------------------------------------------------------------------------------- 1 | export interface ConversationState { 2 | phoneNumber: string; 3 | language: string; 4 | businessType: 'ecommerce' | 'bank' | 'telecom' | 'sme'; 5 | currentFlow: string; 6 | stepInFlow: number; 7 | userData: Record; 8 | lastActivity: Date; 9 | } 10 | 11 | export interface BusinessFlow { 12 | id: string; 13 | name: string; 14 | businessType: string[]; 15 | steps: FlowStep[]; 16 | } 17 | 18 | export interface FlowStep { 19 | id: string; 20 | message: string; 21 | expectedInputType: 'text' | 'number' | 'choice' | 'order_number' | 'phone' | 'email'; 22 | choices?: string[]; 23 | nextStep?: string; 24 | action?: string; 25 | validation?: RegExp; 26 | } 27 | 28 | export const businessFlows: BusinessFlow[] = [ 29 | { 30 | id: 'order_tracking', 31 | name: 'Order Tracking', 32 | businessType: ['ecommerce'], 33 | steps: [ 34 | { 35 | id: 'request_order_number', 36 | message: 'track_order', 37 | expectedInputType: 'text', 38 | validation: /^[A-Z0-9]{6,20}$/, 39 | nextStep: 'show_order_status' 40 | }, 41 | { 42 | id: 'show_order_status', 43 | message: 'order_found', 44 | expectedInputType: 'choice', 45 | choices: ['1. Track another order', '2. Main menu', '3. Contact support'], 46 | action: 'display_order_status' 47 | } 48 | ] 49 | }, 50 | { 51 | id: 'payment_help', 52 | name: 'Payment Assistance', 53 | businessType: ['ecommerce', 'bank', 'sme'], 54 | steps: [ 55 | { 56 | id: 'payment_issue_type', 57 | message: 'Select payment issue:\n1. Failed payment\n2. Refund request\n3. Payment verification\n4. Other', 58 | expectedInputType: 'choice', 59 | choices: ['1', '2', '3', '4'], 60 | nextStep: 'handle_payment_issue' 61 | }, 62 | { 63 | id: 'handle_payment_issue', 64 | message: 'payment_issue_handled', 65 | expectedInputType: 'choice', 66 | choices: ['1. Main menu', '2. Contact support'], 67 | action: 'handle_payment_request' 68 | } 69 | ] 70 | }, 71 | { 72 | id: 'account_services', 73 | name: 'Account Services', 74 | businessType: ['bank', 'telecom'], 75 | steps: [ 76 | { 77 | id: 'account_service_type', 78 | message: 'Select service:\n1. Check balance\n2. Transfer money\n3. Bill payment\n4. Account statement', 79 | expectedInputType: 'choice', 80 | choices: ['1', '2', '3', '4'], 81 | nextStep: 'handle_account_service' 82 | }, 83 | { 84 | id: 'handle_account_service', 85 | message: 'account_service_handled', 86 | expectedInputType: 'choice', 87 | choices: ['1. Another service', '2. Main menu', '3. Contact support'], 88 | action: 'process_account_service' 89 | } 90 | ] 91 | }, 92 | { 93 | id: 'telecom_services', 94 | name: 'Telecom Services', 95 | businessType: ['telecom'], 96 | steps: [ 97 | { 98 | id: 'telecom_service_type', 99 | message: 'Select service:\n1. Data plans\n2. Airtime purchase\n3. Network issues\n4. Bill payment', 100 | expectedInputType: 'choice', 101 | choices: ['1', '2', '3', '4'], 102 | nextStep: 'handle_telecom_service' 103 | }, 104 | { 105 | id: 'handle_telecom_service', 106 | message: 'telecom_service_handled', 107 | expectedInputType: 'choice', 108 | choices: ['1. Another service', '2. Main menu', '3. Contact support'], 109 | action: 'process_telecom_service' 110 | } 111 | ] 112 | }, 113 | { 114 | id: 'general_support', 115 | name: 'General Support', 116 | businessType: ['ecommerce', 'bank', 'telecom', 'sme'], 117 | steps: [ 118 | { 119 | id: 'support_type', 120 | message: 'How can we help?\n1. General inquiry\n2. File complaint\n3. Provide feedback\n4. Business hours', 121 | expectedInputType: 'choice', 122 | choices: ['1', '2', '3', '4'], 123 | nextStep: 'handle_support_request' 124 | }, 125 | { 126 | id: 'handle_support_request', 127 | message: 'support_request_handled', 128 | expectedInputType: 'choice', 129 | choices: ['1. Main menu', '2. Contact human agent'], 130 | action: 'process_support_request' 131 | } 132 | ] 133 | } 134 | ]; -------------------------------------------------------------------------------- /src/services/LanguageManager.ts: -------------------------------------------------------------------------------- 1 | import { translations, Translation } from '../languages/translations'; 2 | 3 | export class LanguageManager { 4 | private supportedLanguages = ['english', 'pidgin', 'yoruba', 'hausa', 'igbo']; 5 | private defaultLanguage = 'english'; 6 | 7 | constructor() {} 8 | 9 | /** 10 | * Get translation for a specific language 11 | */ 12 | getTranslation(language: string): Translation { 13 | const lang = language.toLowerCase(); 14 | return translations[lang] || translations[this.defaultLanguage]; 15 | } 16 | 17 | /** 18 | * Get translated message with variable substitution 19 | */ 20 | getMessage(language: string, key: keyof Translation, variables: Record = {}): string { 21 | const translation = this.getTranslation(language); 22 | let message = translation[key]; 23 | 24 | // Substitute variables in the message 25 | Object.keys(variables).forEach(variable => { 26 | message = message.replace(`{${variable}}`, variables[variable]); 27 | }); 28 | 29 | return message; 30 | } 31 | 32 | /** 33 | * Detect language from user input 34 | */ 35 | detectLanguage(text: string): string { 36 | const lowercaseText = text.toLowerCase(); 37 | 38 | // Yoruba language detection 39 | if (this.containsYorubaWords(lowercaseText)) { 40 | return 'yoruba'; 41 | } 42 | 43 | // Hausa language detection 44 | if (this.containsHausaWords(lowercaseText)) { 45 | return 'hausa'; 46 | } 47 | 48 | // Igbo language detection 49 | if (this.containsIgboWords(lowercaseText)) { 50 | return 'igbo'; 51 | } 52 | 53 | // Pidgin English detection 54 | if (this.containsPidginWords(lowercaseText)) { 55 | return 'pidgin'; 56 | } 57 | 58 | // Default to English 59 | return 'english'; 60 | } 61 | 62 | /** 63 | * Check if language is supported 64 | */ 65 | isSupportedLanguage(language: string): boolean { 66 | return this.supportedLanguages.includes(language.toLowerCase()); 67 | } 68 | 69 | /** 70 | * Get language from number choice 71 | */ 72 | getLanguageFromChoice(choice: string): string | null { 73 | const choices: Record = { 74 | '1': 'english', 75 | '2': 'pidgin', 76 | '3': 'yoruba', 77 | '4': 'hausa', 78 | '5': 'igbo' 79 | }; 80 | return choices[choice] || null; 81 | } 82 | 83 | /** 84 | * Get all supported languages 85 | */ 86 | getSupportedLanguages(): string[] { 87 | return this.supportedLanguages; 88 | } 89 | 90 | private containsYorubaWords(text: string): boolean { 91 | const yorubaWords = [ 92 | 'bawo', 'kaabo', 'elo', 'pele', 'dupe', 'omo', 'oko', 'iya', 'baba', 93 | 'se', 'ni', 'wa', 'si', 'ti', 'fun', 'gbogbo', 'awon', 'kin', 'lo', 94 | 'ran', 'lowo', 'jowo', 'owo', 'yan', 'pada', 'tun', 'gbe', 'maa' 95 | ]; 96 | return yorubaWords.some(word => text.includes(word)); 97 | } 98 | 99 | private containsHausaWords(text: string): boolean { 100 | const hausaWords = [ 101 | 'yaya', 'barka', 'sanu', 'na', 'gode', 'don', 'allah', 'zan', 'iya', 102 | 'taimaka', 'muku', 'yau', 'zuwa', 'kada', 'dan', 'uba', 'uwa', 'yaro', 103 | 'yariya', 'kowa', 'komai', 'lokaci', 'harshe', 'rubutu', 'karanta' 104 | ]; 105 | return hausaWords.some(word => text.includes(word)); 106 | } 107 | 108 | private containsIgboWords(text: string): boolean { 109 | const igboWords = [ 110 | 'kedu', 'nnoo', 'ndewo', 'dalú', 'mee', 'nke', 'onye', 'nwa', 'nne', 111 | 'nna', 'aha', 'ebe', 'mgbe', 'ihe', 'ka', 'na', 'ga', 'nye', 'aka', 112 | 'umu', 'obodo', 'ulo', 'ego', 'oru', 'oge', 'afo', 'ubochi' 113 | ]; 114 | return igboWords.some(word => text.includes(word)); 115 | } 116 | 117 | private containsPidginWords(text: string): boolean { 118 | const pidginWords = [ 119 | 'wetin', 'how', 'far', 'dey', 'no', 'be', 'fit', 'make', 'abi', 'sha', 120 | 'naija', 'bros', 'sister', 'wahala', 'chop', 'oya', 'abeg', 'koro', 121 | 'sabi', 'yarn', 'gist', 'kpele', 'sharp', 'correct', 'comot', 'enter' 122 | ]; 123 | return pidginWords.some(word => text.includes(word)); 124 | } 125 | } -------------------------------------------------------------------------------- /src/services/WhatsAppService.ts: -------------------------------------------------------------------------------- 1 | import { Client, LocalAuth, Message } from 'whatsapp-web.js'; 2 | import * as QRCode from 'qrcode-terminal'; 3 | import { ConversationController } from '../controllers/ConversationController'; 4 | 5 | export class WhatsAppService { 6 | private client: Client; 7 | private conversationController: ConversationController; 8 | private isReady: boolean = false; 9 | 10 | constructor() { 11 | this.client = new Client({ 12 | authStrategy: new LocalAuth({ 13 | clientId: process.env.WHATSAPP_SESSION_NAME || 'customer-support-bot' 14 | }), 15 | puppeteer: { 16 | headless: true, 17 | args: [ 18 | '--no-sandbox', 19 | '--disable-setuid-sandbox', 20 | '--disable-dev-shm-usage', 21 | '--disable-accelerated-2d-canvas', 22 | '--no-first-run', 23 | '--no-zygote', 24 | '--disable-gpu' 25 | ] 26 | } 27 | }); 28 | 29 | this.conversationController = new ConversationController(); 30 | this.setupEventHandlers(); 31 | } 32 | 33 | private setupEventHandlers(): void { 34 | // QR Code for authentication 35 | this.client.on('qr', (qr) => { 36 | console.log('Scan this QR code with your WhatsApp:'); 37 | QRCode.generate(qr, { small: true }); 38 | }); 39 | 40 | // Client ready 41 | this.client.on('ready', () => { 42 | console.log('✅ WhatsApp Customer Support Bot is ready!'); 43 | this.isReady = true; 44 | }); 45 | 46 | // Handle incoming messages 47 | this.client.on('message', async (message: Message) => { 48 | await this.handleMessage(message); 49 | }); 50 | 51 | // Handle authentication success 52 | this.client.on('authenticated', () => { 53 | console.log('✅ WhatsApp authenticated successfully'); 54 | }); 55 | 56 | // Handle authentication failure 57 | this.client.on('auth_failure', (msg) => { 58 | console.error('❌ Authentication failed:', msg); 59 | }); 60 | 61 | // Handle disconnection 62 | this.client.on('disconnected', (reason) => { 63 | console.log('❌ WhatsApp disconnected:', reason); 64 | this.isReady = false; 65 | }); 66 | } 67 | 68 | private async handleMessage(message: Message): Promise { 69 | try { 70 | // Skip messages from groups or status updates 71 | if (message.from.includes('@g.us') || message.from.includes('status@broadcast')) { 72 | return; 73 | } 74 | 75 | // Skip messages sent by the bot itself 76 | if (message.fromMe) { 77 | return; 78 | } 79 | 80 | const phoneNumber = message.from.replace('@c.us', ''); 81 | const messageText = message.body.trim(); 82 | 83 | console.log(`📨 Message from ${phoneNumber}: ${messageText}`); 84 | 85 | // Process message through conversation controller 86 | const response = await this.conversationController.processMessage( 87 | phoneNumber, 88 | messageText 89 | ); 90 | 91 | // Send response 92 | if (response) { 93 | await this.sendMessage(phoneNumber, response); 94 | } 95 | 96 | } catch (error) { 97 | console.error('Error handling message:', error); 98 | await this.sendMessage( 99 | message.from.replace('@c.us', ''), 100 | 'Sorry, I encountered an error. Please try again or contact support.' 101 | ); 102 | } 103 | } 104 | 105 | public async sendMessage(phoneNumber: string, message: string): Promise { 106 | try { 107 | if (!this.isReady) { 108 | console.log('WhatsApp client not ready, queuing message...'); 109 | return; 110 | } 111 | 112 | const chatId = `${phoneNumber}@c.us`; 113 | await this.client.sendMessage(chatId, message); 114 | console.log(`📤 Sent to ${phoneNumber}: ${message.substring(0, 50)}...`); 115 | 116 | } catch (error) { 117 | console.error('Error sending message:', error); 118 | } 119 | } 120 | 121 | public async sendMessageWithButtons( 122 | phoneNumber: string, 123 | message: string, 124 | buttons: string[] 125 | ): Promise { 126 | try { 127 | if (!this.isReady) { 128 | console.log('WhatsApp client not ready, queuing message...'); 129 | return; 130 | } 131 | 132 | const chatId = `${phoneNumber}@c.us`; 133 | 134 | // For now, send as regular message with numbered options 135 | // WhatsApp Web.js buttons require business account 136 | const buttonText = buttons.map((btn, index) => `${index + 1}. ${btn}`).join('\n'); 137 | const fullMessage = `${message}\n\n${buttonText}`; 138 | 139 | await this.client.sendMessage(chatId, fullMessage); 140 | console.log(`📤 Sent buttons to ${phoneNumber}`); 141 | 142 | } catch (error) { 143 | console.error('Error sending message with buttons:', error); 144 | // Fallback to regular message 145 | await this.sendMessage(phoneNumber, message); 146 | } 147 | } 148 | 149 | public async initialize(): Promise { 150 | try { 151 | console.log('🚀 Initializing WhatsApp Customer Support Bot...'); 152 | await this.client.initialize(); 153 | } catch (error) { 154 | console.error('Failed to initialize WhatsApp client:', error); 155 | throw error; 156 | } 157 | } 158 | 159 | public async destroy(): Promise { 160 | try { 161 | await this.client.destroy(); 162 | this.isReady = false; 163 | console.log('WhatsApp client destroyed'); 164 | } catch (error) { 165 | console.error('Error destroying WhatsApp client:', error); 166 | } 167 | } 168 | 169 | public isClientReady(): boolean { 170 | return this.isReady; 171 | } 172 | 173 | public getClient(): Client { 174 | return this.client; 175 | } 176 | } -------------------------------------------------------------------------------- /src/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | export class Logger { 2 | static info(message: string, data?: any): void { 3 | const timestamp = new Date().toISOString(); 4 | console.log(`[${timestamp}] INFO: ${message}`, data ? JSON.stringify(data) : ''); 5 | } 6 | 7 | static error(message: string, error?: any): void { 8 | const timestamp = new Date().toISOString(); 9 | console.error(`[${timestamp}] ERROR: ${message}`, error || ''); 10 | } 11 | 12 | static warn(message: string, data?: any): void { 13 | const timestamp = new Date().toISOString(); 14 | console.warn(`[${timestamp}] WARN: ${message}`, data ? JSON.stringify(data) : ''); 15 | } 16 | 17 | static debug(message: string, data?: any): void { 18 | if (process.env.NODE_ENV === 'development') { 19 | const timestamp = new Date().toISOString(); 20 | console.debug(`[${timestamp}] DEBUG: ${message}`, data ? JSON.stringify(data) : ''); 21 | } 22 | } 23 | } 24 | 25 | export class BusinessHoursChecker { 26 | private timezone: string; 27 | private hours: string; 28 | 29 | constructor(timezone = 'Africa/Lagos', hours = '9:00-18:00') { 30 | this.timezone = timezone; 31 | this.hours = hours; 32 | } 33 | 34 | isWithinBusinessHours(): boolean { 35 | try { 36 | const now = new Date(); 37 | const timeInTimezone = new Intl.DateTimeFormat('en-US', { 38 | timeZone: this.timezone, 39 | hour: '2-digit', 40 | minute: '2-digit', 41 | hour12: false 42 | }).format(now); 43 | 44 | const [currentHour, currentMinute] = timeInTimezone.split(':').map(Number); 45 | const currentTimeMinutes = currentHour * 60 + currentMinute; 46 | 47 | const [startTime, endTime] = this.hours.split('-'); 48 | const [startHour, startMinute] = startTime.split(':').map(Number); 49 | const [endHour, endMinute] = endTime.split(':').map(Number); 50 | 51 | const startTimeMinutes = startHour * 60 + startMinute; 52 | const endTimeMinutes = endHour * 60 + endMinute; 53 | 54 | return currentTimeMinutes >= startTimeMinutes && currentTimeMinutes <= endTimeMinutes; 55 | } catch (error) { 56 | Logger.error('Error checking business hours', error); 57 | return true; // Default to open if error 58 | } 59 | } 60 | 61 | getBusinessHoursMessage(language: string): string { 62 | const isOpen = this.isWithinBusinessHours(); 63 | const status = isOpen ? 'open' : 'closed'; 64 | 65 | return `We are currently ${status}. Business hours: ${this.hours} (${this.timezone})`; 66 | } 67 | } 68 | 69 | export class InputValidator { 70 | static isValidPhoneNumber(phone: string): boolean { 71 | // Nigerian phone number validation 72 | const phoneRegex = /^(\+234|234|0)[7-9][0-1]\d{8}$/; 73 | return phoneRegex.test(phone.replace(/\s+/g, '')); 74 | } 75 | 76 | static isValidEmail(email: string): boolean { 77 | const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 78 | return emailRegex.test(email); 79 | } 80 | 81 | static isValidOrderNumber(orderNumber: string): boolean { 82 | // Flexible order number validation 83 | const orderRegex = /^[A-Z0-9]{6,20}$/i; 84 | return orderRegex.test(orderNumber); 85 | } 86 | 87 | static sanitizeInput(input: string): string { 88 | return input.trim().replace(/[<>\"'&]/g, ''); 89 | } 90 | } 91 | 92 | export class MessageFormatter { 93 | static formatCurrency(amount: number, currency = 'NGN'): string { 94 | return new Intl.NumberFormat('en-NG', { 95 | style: 'currency', 96 | currency: currency, 97 | minimumFractionDigits: 2 98 | }).format(amount); 99 | } 100 | 101 | static formatPhoneNumber(phone: string): string { 102 | // Format Nigerian phone numbers 103 | const cleaned = phone.replace(/\D/g, ''); 104 | if (cleaned.startsWith('234')) { 105 | return `+${cleaned}`; 106 | } else if (cleaned.startsWith('0')) { 107 | return `+234${cleaned.substring(1)}`; 108 | } 109 | return phone; 110 | } 111 | 112 | static truncateMessage(message: string, maxLength = 1000): string { 113 | if (message.length <= maxLength) return message; 114 | return message.substring(0, maxLength - 3) + '...'; 115 | } 116 | } 117 | 118 | export class SessionManager { 119 | private static sessions: Map = new Map(); 120 | 121 | static setSession(phoneNumber: string, key: string, value: any): void { 122 | const sessionKey = `${phoneNumber}_${key}`; 123 | this.sessions.set(sessionKey, { 124 | value, 125 | timestamp: new Date() 126 | }); 127 | } 128 | 129 | static getSession(phoneNumber: string, key: string): any { 130 | const sessionKey = `${phoneNumber}_${key}`; 131 | const session = this.sessions.get(sessionKey); 132 | return session ? session.value : null; 133 | } 134 | 135 | static clearSession(phoneNumber: string, key?: string): void { 136 | if (key) { 137 | const sessionKey = `${phoneNumber}_${key}`; 138 | this.sessions.delete(sessionKey); 139 | } else { 140 | // Clear all sessions for this phone number 141 | for (const [sessionKey] of this.sessions) { 142 | if (sessionKey.startsWith(phoneNumber)) { 143 | this.sessions.delete(sessionKey); 144 | } 145 | } 146 | } 147 | } 148 | 149 | static cleanupExpiredSessions(maxAgeHours = 24): void { 150 | const cutoffTime = new Date(Date.now() - maxAgeHours * 60 * 60 * 1000); 151 | 152 | for (const [key, session] of this.sessions) { 153 | if (session.timestamp < cutoffTime) { 154 | this.sessions.delete(key); 155 | } 156 | } 157 | } 158 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "libReplacement": true, /* Enable lib replacement. */ 18 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 19 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 20 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 21 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 22 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 23 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 24 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 25 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 26 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 27 | 28 | /* Modules */ 29 | "module": "commonjs", /* Specify what module code is generated. */ 30 | // "rootDir": "./", /* Specify the root folder within your source files. */ 31 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 32 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 33 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 34 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 35 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 36 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 37 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 38 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 39 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 40 | // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ 41 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 42 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 43 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 44 | // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ 45 | // "resolveJsonModule": true, /* Enable importing .json files. */ 46 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 47 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 48 | 49 | /* JavaScript Support */ 50 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 51 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 52 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 53 | 54 | /* Emit */ 55 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 56 | "declarationMap": true, /* Create sourcemaps for d.ts files. */ 57 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 58 | "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 59 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 62 | "outDir": "./dist", /* Specify an output folder for all emitted files. */ 63 | // "removeComments": true, /* Disable emitting comments. */ 64 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 65 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 66 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 67 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 68 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 69 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 70 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 71 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 72 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 73 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 74 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 75 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 76 | 77 | /* Interop Constraints */ 78 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 79 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 80 | // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ 81 | // "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */ 82 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 83 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 84 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 85 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 86 | 87 | /* Type Checking */ 88 | "strict": true, /* Enable all strict type-checking options. */ 89 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 90 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 91 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 92 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 93 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 94 | // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ 95 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 96 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 97 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 98 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 99 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 100 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 101 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 102 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 103 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 104 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 105 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 106 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 107 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 108 | 109 | /* Completeness */ 110 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 111 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 112 | }, 113 | "include": ["src/**/*"], 114 | "exclude": ["node_modules", "dist"] 115 | } 116 | --------------------------------------------------------------------------------