Compare commits
No commits in common. "8cfb624f2e1d753752704b6dbd958e3f2a1fed9f" and "c8d39c0e6232ca17118a6f106182c38b7159ccac" have entirely different histories.
8cfb624f2e
...
c8d39c0e62
18 changed files with 179 additions and 5141 deletions
273
README.md
273
README.md
|
|
@ -1,17 +1,10 @@
|
||||||
# General Bots Documentation (BotBook)
|
# General Bots
|
||||||
|
|
||||||
**Version:** 6.2.0
|
|
||||||
**Purpose:** Comprehensive documentation for General Bots (mdBook format)
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
**Enterprise-Grade LLM Orchestrator & AI Automation Platform**
|
||||||
|
|
||||||
## Overview
|
A strongly-typed, self-hosted conversational platform focused on convention over configuration and code-less approaches.
|
||||||
|
|
||||||
BotBook is the official documentation repository for General Bots, built using [mdBook](https://rust-lang.github.io/mdBook/). It provides comprehensive guides, API references, tutorials, and architectural documentation for the entire General Bots platform - an enterprise-grade LLM orchestrator and AI automation platform.
|
|
||||||
|
|
||||||
For the latest live documentation, visit **[docs.pragmatismo.com.br](https://docs.pragmatismo.com.br)**.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -21,7 +14,7 @@ For the latest live documentation, visit **[docs.pragmatismo.com.br](https://doc
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📦 General Bots Repositories
|
## 📦 Repositories
|
||||||
|
|
||||||
| Repository | Description | Status |
|
| Repository | Description | Status |
|
||||||
|------------|-------------|--------|
|
|------------|-------------|--------|
|
||||||
|
|
@ -40,7 +33,6 @@ For the latest live documentation, visit **[docs.pragmatismo.com.br](https://doc
|
||||||
|
|
||||||
- **Rust** (latest stable) - [Install from rustup.rs](https://rustup.rs/)
|
- **Rust** (latest stable) - [Install from rustup.rs](https://rustup.rs/)
|
||||||
- **Git** - [Download from git-scm.com](https://git-scm.com/downloads)
|
- **Git** - [Download from git-scm.com](https://git-scm.com/downloads)
|
||||||
- **mdBook** - `cargo install mdbook`
|
|
||||||
|
|
||||||
### Run the Server
|
### Run the Server
|
||||||
|
|
||||||
|
|
@ -71,20 +63,6 @@ cd botapp
|
||||||
cargo tauri dev
|
cargo tauri dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build Documentation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Clone botbook
|
|
||||||
git clone https://github.com/GeneralBots/botbook
|
|
||||||
cd botbook
|
|
||||||
|
|
||||||
# Build documentation
|
|
||||||
mdbook build
|
|
||||||
|
|
||||||
# Serve locally with hot reload
|
|
||||||
mdbook serve --open
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ✨ Key Features
|
## ✨ Key Features
|
||||||
|
|
@ -122,182 +100,6 @@ CLEAR TOOLS ' Remove all tools from session
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📁 Documentation Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
botbook/
|
|
||||||
├── book.toml # mdBook configuration
|
|
||||||
├── src/
|
|
||||||
│ ├── SUMMARY.md # Table of contents
|
|
||||||
│ ├── README.md # Introduction
|
|
||||||
│ ├── 01-introduction/ # Quick Start
|
|
||||||
│ ├── 02-templates/ # Package System
|
|
||||||
│ ├── 03-knowledge-base/ # Knowledge Base
|
|
||||||
│ ├── 04-gbui/ # UI Interface
|
|
||||||
│ ├── 06-gbdialog/ # BASIC Dialogs
|
|
||||||
│ ├── 08-config/ # Configuration
|
|
||||||
│ ├── 10-rest/ # REST API
|
|
||||||
│ ├── 12-auth/ # Authentication
|
|
||||||
│ └── assets/ # Images, diagrams
|
|
||||||
├── i18n/ # Translations
|
|
||||||
└── book/ # Generated output
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Documentation Writing Guidelines
|
|
||||||
|
|
||||||
### ✅ Keyword Naming Rules - MANDATORY
|
|
||||||
|
|
||||||
**Keywords NEVER use underscores. Always use spaces.**
|
|
||||||
|
|
||||||
| Write This | NOT This |
|
|
||||||
|------------|----------|
|
|
||||||
| `SEND MAIL` | `SEND_MAIL` |
|
|
||||||
| `GENERATE PDF` | `GENERATE_PDF` |
|
|
||||||
| `MERGE PDF` | `MERGE_PDF` |
|
|
||||||
| `DELETE` | `DELETE_HTTP` |
|
|
||||||
| `SET HEADER` | `SET_HEADER` |
|
|
||||||
| `FOR EACH` | `FOR_EACH` |
|
|
||||||
|
|
||||||
#### Correct Syntax Examples
|
|
||||||
```basic
|
|
||||||
SEND MAIL to, subject, body, attachments
|
|
||||||
GENERATE PDF template, data, output
|
|
||||||
MERGE PDF files, output
|
|
||||||
DELETE "url"
|
|
||||||
ON ERROR RESUME NEXT
|
|
||||||
SET BOT MEMORY key, value
|
|
||||||
KB STATISTICS
|
|
||||||
```
|
|
||||||
|
|
||||||
#### ❌ NEVER Use Underscores
|
|
||||||
```basic
|
|
||||||
SEND_MAIL ' WRONG!
|
|
||||||
GENERATE_PDF ' WRONG!
|
|
||||||
DELETE_HTTP ' WRONG!
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 🎨 Official Icons - MANDATORY
|
|
||||||
|
|
||||||
**NEVER generate icons with LLM. Use official SVG icons from `botui/ui/suite/assets/icons/`**
|
|
||||||
|
|
||||||
#### Usage in Documentation
|
|
||||||
```markdown
|
|
||||||
<!-- Reference icons in docs -->
|
|
||||||

|
|
||||||
|
|
||||||
<!-- With HTML for sizing -->
|
|
||||||
<img src="../assets/icons/gb-analytics.svg" alt="Analytics" width="24">
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Required Icons
|
|
||||||
```
|
|
||||||
ui/suite/assets/icons/
|
|
||||||
├── gb-logo.svg # Main GB logo
|
|
||||||
├── gb-bot.svg # Bot/assistant
|
|
||||||
├── gb-analytics.svg # Analytics
|
|
||||||
├── gb-calendar.svg # Calendar
|
|
||||||
├── gb-chat.svg # Chat
|
|
||||||
├── gb-drive.svg # File storage
|
|
||||||
├── gb-mail.svg # Email
|
|
||||||
├── gb-meet.svg # Video meetings
|
|
||||||
├── gb-tasks.svg # Task management
|
|
||||||
└── ...
|
|
||||||
```
|
|
||||||
|
|
||||||
All icons use `stroke="currentColor"` for CSS theming.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 🚫 NO ASCII Diagramrams - MANDATORY
|
|
||||||
|
|
||||||
**NEVER use ASCII art diagrams. ALL diagrams must be SVG.**
|
|
||||||
|
|
||||||
#### ❌ Prohibited ASCII Patterns
|
|
||||||
```
|
|
||||||
┌─────────┐ ╔═══════╗ +-------+
|
|
||||||
│ Box │ ║ Box ║ | Box |
|
|
||||||
└─────────┘ ╚═══════╝ +-------+
|
|
||||||
```
|
|
||||||
|
|
||||||
#### ✅ What to Use Instead
|
|
||||||
|
|
||||||
| Instead of... | Use... |
|
|
||||||
|---------------|--------|
|
|
||||||
| ASCII box diagrams | SVG diagrams in `assets/` |
|
|
||||||
| ASCII flow charts | SVG with arrows and boxes |
|
|
||||||
| ASCII directory trees | Markdown tables |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 🎨 SVG Diagram Guidelines
|
|
||||||
|
|
||||||
All SVGs must support light/dark modes:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<style>
|
|
||||||
.title-text { fill: #1E1B4B; }
|
|
||||||
.main-text { fill: #334155; }
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.title-text { fill: #F1F5F9; }
|
|
||||||
.main-text { fill: #E2E8F0; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 💬 Conversation Examples
|
|
||||||
|
|
||||||
Use WhatsApp-style HTML format for bot interactions:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="wa-chat">
|
|
||||||
<div class="wa-message bot">
|
|
||||||
<div class="wa-bubble">
|
|
||||||
<p>Hello! How can I help?</p>
|
|
||||||
<div class="wa-time">10:30</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="wa-message user">
|
|
||||||
<div class="wa-bubble">
|
|
||||||
<p>I want to enroll</p>
|
|
||||||
<div class="wa-time">10:31</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 📋 Source Code References
|
|
||||||
|
|
||||||
| Topic | Source Location |
|
|
||||||
|-------|-----------------|
|
|
||||||
| BASIC Keywords | `botserver/src/basic/keywords/` |
|
|
||||||
| Database Models | `botserver/src/shared/models.rs` |
|
|
||||||
| API Routes | `botserver/src/core/urls.rs` |
|
|
||||||
| Configuration | `botserver/src/core/config/` |
|
|
||||||
| Templates | `botserver/templates/` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 📖 Documentation Accuracy Rules
|
|
||||||
|
|
||||||
```
|
|
||||||
- All documentation MUST match actual source code
|
|
||||||
- Extract real keywords from botserver/src/basic/keywords/
|
|
||||||
- Use actual examples from botserver/templates/
|
|
||||||
- Version numbers must be 6.2.0
|
|
||||||
- No placeholder content - only verified features
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏛️ Architecture Details
|
## 🏛️ Architecture Details
|
||||||
|
|
||||||
### botserver (Core)
|
### botserver (Core)
|
||||||
|
|
@ -331,14 +133,31 @@ Common Rust code shared across projects:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛡️ Security
|
## 🔧 Development Setup
|
||||||
|
|
||||||
- **AGPL-3.0 License** - True open source with contribution requirements
|
```bash
|
||||||
- **Self-hosted** - Your data stays on your infrastructure
|
# Clone all repositories
|
||||||
- **Enterprise-grade** - 5+ years of stability
|
git clone https://github.com/GeneralBots/botserver botserver
|
||||||
- **No vendor lock-in** - Open protocols and standards
|
git clone https://github.com/GeneralBots/botui
|
||||||
|
git clone https://github.com/GeneralBots/botapp
|
||||||
|
git clone https://github.com/GeneralBots/botlib
|
||||||
|
git clone https://github.com/GeneralBots/botbook botbook
|
||||||
|
|
||||||
Report security issues to: **security@pragmatismo.com.br**
|
# Build all (from each directory)
|
||||||
|
cd botlib && cargo build
|
||||||
|
cd ../botserver && cargo build
|
||||||
|
cd ../botui && cargo build
|
||||||
|
cd ../botapp && cargo build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📖 Documentation
|
||||||
|
|
||||||
|
- **[Complete Documentation](https://github.com/GeneralBots/botbook)** - Full mdBook documentation
|
||||||
|
- **[Quick Start Guide](https://github.com/GeneralBots/botserver/blob/main/docs/QUICK_START.md)** - Get started in minutes
|
||||||
|
- **[API Reference](https://github.com/GeneralBots/botserver/blob/main/docs/src/chapter-10-api/README.md)** - REST API documentation
|
||||||
|
- **[Architecture Guide](https://github.com/GeneralBots/botserver/blob/main/docs/src/chapter-07-gbapp/README.md)** - System architecture
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -353,16 +172,14 @@ Report security issues to: **security@pragmatismo.com.br**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔗 Links
|
## 🛡️ Security
|
||||||
|
|
||||||
- **Website:** [pragmatismo.com.br](https://pragmatismo.com.br)
|
- **AGPL-3.0 License** - True open source with contribution requirements
|
||||||
- **Documentation:** [docs.pragmatismo.com.br](https://docs.pragmatismo.com.br)
|
- **Self-hosted** - Your data stays on your infrastructure
|
||||||
- **BotBook:** [Complete Documentation](https://github.com/GeneralBots/botbook)
|
- **Enterprise-grade** - 5+ years of stability
|
||||||
- **Quick Start:** [Get Started in Minutes](https://github.com/GeneralBots/botserver/blob/main/docs/QUICK_START.md)
|
- **No vendor lock-in** - Open protocols and standards
|
||||||
- **API Reference:** [REST API Documentation](https://github.com/GeneralBots/botserver/blob/main/docs/src/chapter-10-api/README.md)
|
|
||||||
- **Architecture:** [System Architecture Guide](https://github.com/GeneralBots/botserver/blob/main/docs/src/chapter-07-gbapp/README.md)
|
Report security issues to: **security@pragmatismo.com.br**
|
||||||
- **Stack Overflow:** Tag questions with `generalbots`
|
|
||||||
- **Video Tutorial:** [7 AI General Bots LLM Templates](https://www.youtube.com/watch?v=KJgvUPXi3Fw)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -378,19 +195,6 @@ We welcome contributions! See our [Contributing Guidelines](https://github.com/G
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔑 Remember
|
|
||||||
|
|
||||||
- **Accuracy** - Must match botserver source code
|
|
||||||
- **Completeness** - No placeholder sections
|
|
||||||
- **Clarity** - Accessible to BASIC enthusiasts
|
|
||||||
- **Keywords** - NEVER use underscores - always spaces
|
|
||||||
- **NO ASCII art** - Use SVG diagrams only
|
|
||||||
- **Official icons** - Use icons from botui/ui/suite/assets/icons/
|
|
||||||
- **Version 6.2.0** - Always reference 6.2.0
|
|
||||||
- **GIT WORKFLOW** - ALWAYS push to ALL repositories (github, pragmatismo)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📄 License
|
## 📄 License
|
||||||
|
|
||||||
General Bots is licensed under **AGPL-3.0**.
|
General Bots is licensed under **AGPL-3.0**.
|
||||||
|
|
@ -401,6 +205,15 @@ Copyright (c) pragmatismo.com.br. All rights reserved.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🔗 Links
|
||||||
|
|
||||||
|
- **Website:** [pragmatismo.com.br](https://pragmatismo.com.br)
|
||||||
|
- **Documentation:** [docs.pragmatismo.com.br](https://docs.pragmatismo.com.br)
|
||||||
|
- **Stack Overflow:** Tag questions with `generalbots`
|
||||||
|
- **Video Tutorial:** [7 AI General Bots LLM Templates](https://www.youtube.com/watch?v=KJgvUPXi3Fw)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
> **Code Name:** [Guaribas](https://en.wikipedia.org/wiki/Guaribas) (a city in Brazil, state of Piauí)
|
> **Code Name:** [Guaribas](https://en.wikipedia.org/wiki/Guaribas) (a city in Brazil, state of Piauí)
|
||||||
>
|
>
|
||||||
> *"No one should have to do work that can be done by a machine."* - Roberto Mangabeira Unger
|
> *"No one should have to do work that can be done by a machine."* - Roberto Mangabeira Unger
|
||||||
|
|
@ -107,7 +107,7 @@ Attendants can be identified by **any channel**: WhatsApp phone, email, Microsof
|
||||||
```csv
|
```csv
|
||||||
id,name,channel,preferences,department,aliases,phone,email,teams,google
|
id,name,channel,preferences,department,aliases,phone,email,teams,google
|
||||||
att-001,João Silva,all,sales,commercial,joao;js;silva,+5511999990001,joao.silva@company.com,joao.silva@company.onmicrosoft.com,joao.silva@company.com
|
att-001,João Silva,all,sales,commercial,joao;js;silva,+5511999990001,joao.silva@company.com,joao.silva@company.onmicrosoft.com,joao.silva@company.com
|
||||||
att-002,Maria Santos,whatsapp,support,customer-service,maria;ms,+5511999990002,santos@company.com,santos@company.onmicrosoft.com,santos@gmail.com
|
att-002,Maria Santos,whatsapp,support,customer-service,maria;ms,+5511999990002,maria.santos@company.com,maria.santos@company.onmicrosoft.com,maria.santos@gmail.com
|
||||||
att-003,Pedro Costa,web,technical,engineering,pedro;pc;tech,+5511999990003,pedro.costa@company.com,pedro.costa@company.onmicrosoft.com,pedro.costa@company.com
|
att-003,Pedro Costa,web,technical,engineering,pedro;pc;tech,+5511999990003,pedro.costa@company.com,pedro.costa@company.onmicrosoft.com,pedro.costa@company.com
|
||||||
att-004,Ana Oliveira,all,collections,finance,ana;ao;cobranca,+5511999990004,ana.oliveira@company.com,ana.oliveira@company.onmicrosoft.com,ana.oliveira@company.com
|
att-004,Ana Oliveira,all,collections,finance,ana;ao;cobranca,+5511999990004,ana.oliveira@company.com,ana.oliveira@company.onmicrosoft.com,ana.oliveira@company.com
|
||||||
att-005,Carlos Souza,whatsapp,sales,commercial,carlos;cs,+5511999990005,carlos.souza@company.com,carlos.souza@company.onmicrosoft.com,carlos.souza@gmail.com
|
att-005,Carlos Souza,whatsapp,sales,commercial,carlos;cs,+5511999990005,carlos.souza@company.com,carlos.souza@company.onmicrosoft.com,carlos.souza@gmail.com
|
||||||
|
|
@ -411,4 +411,4 @@ Set up alerts for:
|
||||||
- [Transfer to Human](../chapter-11-features/transfer-to-human.md) - Handoff details
|
- [Transfer to Human](../chapter-11-features/transfer-to-human.md) - Handoff details
|
||||||
- [LLM-Assisted Attendant](../chapter-11-features/attendant-llm-assist.md) - AI copilot features
|
- [LLM-Assisted Attendant](../chapter-11-features/attendant-llm-assist.md) - AI copilot features
|
||||||
- [Sales CRM Template](./template-crm.md) - Full CRM without attendance
|
- [Sales CRM Template](./template-crm.md) - Full CRM without attendance
|
||||||
- [Attendance Queue Module](../appendix-external-services/attendance-queue.md) - Queue configuration
|
- [Attendance Queue Module](../appendix-external-services/attendance-queue.md) - Queue configuration
|
||||||
|
|
@ -62,7 +62,7 @@ Add these settings to your `config.csv`:
|
||||||
</div>
|
</div>
|
||||||
<div class="wa-message user">
|
<div class="wa-message user">
|
||||||
<div class="wa-bubble">
|
<div class="wa-bubble">
|
||||||
<p>santos@company.com</p>
|
<p>maria.santos@company.com</p>
|
||||||
<div class="wa-time">10:33</div>
|
<div class="wa-time">10:33</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -97,7 +97,7 @@ Add these settings to your `config.csv`:
|
||||||
<p>📋 <strong>Contact Details</strong></p>
|
<p>📋 <strong>Contact Details</strong></p>
|
||||||
<p>━━━━━━━━━━━━━━━━━</p>
|
<p>━━━━━━━━━━━━━━━━━</p>
|
||||||
<p>👤 <strong>Name:</strong> Maria Santos</p>
|
<p>👤 <strong>Name:</strong> Maria Santos</p>
|
||||||
<p>📧 <strong>Email:</strong> santos@company.com</p>
|
<p>📧 <strong>Email:</strong> maria.santos@company.com</p>
|
||||||
<p>📱 <strong>Phone:</strong> +55 11 98765-4321</p>
|
<p>📱 <strong>Phone:</strong> +55 11 98765-4321</p>
|
||||||
<p>🏢 <strong>Company:</strong> Tech Solutions Ltd</p>
|
<p>🏢 <strong>Company:</strong> Tech Solutions Ltd</p>
|
||||||
<p>🏷️ <strong>Tags:</strong> lead</p>
|
<p>🏷️ <strong>Tags:</strong> lead</p>
|
||||||
|
|
@ -139,7 +139,7 @@ Add these settings to your `config.csv`:
|
||||||
<div class="wa-bubble">
|
<div class="wa-bubble">
|
||||||
<p>🔍 Found 3 contacts at "Tech Solutions":</p>
|
<p>🔍 Found 3 contacts at "Tech Solutions":</p>
|
||||||
<p></p>
|
<p></p>
|
||||||
<p>1. <strong>Maria Santos</strong> - santos@company.com</p>
|
<p>1. <strong>Maria Santos</strong> - maria.santos@company.com</p>
|
||||||
<p> 📱 +55 11 98765-4321 | 🏷️ lead</p>
|
<p> 📱 +55 11 98765-4321 | 🏷️ lead</p>
|
||||||
<p></p>
|
<p></p>
|
||||||
<p>2. <strong>João Silva</strong> - joao.silva@techsolutions.com</p>
|
<p>2. <strong>João Silva</strong> - joao.silva@techsolutions.com</p>
|
||||||
|
|
@ -300,4 +300,4 @@ Use the `POST` and `GET` keywords to sync contacts with Salesforce, HubSpot, or
|
||||||
.wa-bubble p{margin:0 0 4px 0;line-height:1.4;color:#303030}
|
.wa-bubble p{margin:0 0 4px 0;line-height:1.4;color:#303030}
|
||||||
.wa-bubble p:last-child{margin-bottom:0}
|
.wa-bubble p:last-child{margin-bottom:0}
|
||||||
.wa-time{font-size:11px;color:#8696a0;text-align:right;margin-top:4px}
|
.wa-time{font-size:11px;color:#8696a0;text-align:right;margin-top:4px}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -51,12 +51,12 @@ pageVariable = "pagina"
|
||||||
limitVariable = "limite"
|
limitVariable = "limite"
|
||||||
|
|
||||||
SEND EMAIL admin, "Syncing categories..."
|
SEND EMAIL admin, "Syncing categories..."
|
||||||
SYNCHRONIZE /categorias/receitas-despesas, CategoriaReceita, Id, pageVariable, limitVariable
|
SYNCHRONIZE /categorias/receitas-despesas, maria.CategoriaReceita, Id, pageVariable, limitVariable
|
||||||
SEND EMAIL admin, REPORT
|
SEND EMAIL admin, REPORT
|
||||||
RESET REPORT
|
RESET REPORT
|
||||||
|
|
||||||
' Sync payment methods
|
' Sync payment methods
|
||||||
SYNCHRONIZE /formas-pagamentos, FormaDePagamento, Id, pageVariable, limitVariable
|
SYNCHRONIZE /formas-pagamentos, maria.FormaDePagamento, Id, pageVariable, limitVariable
|
||||||
SEND EMAIL admin, REPORT
|
SEND EMAIL admin, REPORT
|
||||||
RESET REPORT
|
RESET REPORT
|
||||||
```
|
```
|
||||||
|
|
@ -69,7 +69,7 @@ Until `SYNCHRONIZE` is implemented, use this pattern:
|
||||||
' Manual sync equivalent
|
' Manual sync equivalent
|
||||||
pageVariable = "pagina"
|
pageVariable = "pagina"
|
||||||
limitVariable = "limite"
|
limitVariable = "limite"
|
||||||
tableName = "CategoriaReceita"
|
tableName = "maria.CategoriaReceita"
|
||||||
endpoint = "/categorias/receitas-despesas"
|
endpoint = "/categorias/receitas-despesas"
|
||||||
|
|
||||||
page = 1
|
page = 1
|
||||||
|
|
@ -112,7 +112,7 @@ TALK "Synced " + totalSynced + " records to " + tableName
|
||||||
When implemented, `SYNCHRONIZE` should:
|
When implemented, `SYNCHRONIZE` should:
|
||||||
|
|
||||||
1. Use the global `host`, `limit`, `pages` variables from config
|
1. Use the global `host`, `limit`, `pages` variables from config
|
||||||
2. Support connection prefixes (e.g., `TableName`)
|
2. Support connection prefixes (e.g., `maria.TableName`)
|
||||||
3. Handle API errors gracefully with retry logic
|
3. Handle API errors gracefully with retry logic
|
||||||
4. Update the `REPORT` variable with sync statistics
|
4. Update the `REPORT` variable with sync statistics
|
||||||
5. Support both REST JSON responses and paginated arrays
|
5. Support both REST JSON responses and paginated arrays
|
||||||
|
|
@ -120,4 +120,4 @@ When implemented, `SYNCHRONIZE` should:
|
||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
- [Script Execution Flow](./script-execution-flow.md) - How config variables are injected
|
- [Script Execution Flow](./script-execution-flow.md) - How config variables are injected
|
||||||
- [Data Operations](./keywords-data.md) - Data manipulation keywords
|
- [Data Operations](./keywords-data.md) - Data manipulation keywords
|
||||||
|
|
@ -5,7 +5,7 @@ The `TABLE` keyword defines database tables directly in your `.bas` files. Table
|
||||||
## Syntax
|
## Syntax
|
||||||
|
|
||||||
```basic
|
```basic
|
||||||
TABLE TableName
|
TABLE TableName ON connection
|
||||||
FieldName dataType[(length[,precision])] [key] [references OtherTable]
|
FieldName dataType[(length[,precision])] [key] [references OtherTable]
|
||||||
...
|
...
|
||||||
END TABLE
|
END TABLE
|
||||||
|
|
@ -70,7 +70,7 @@ conn-maria-Driver,mariadb
|
||||||
### Basic Table Definition
|
### Basic Table Definition
|
||||||
|
|
||||||
```basic
|
```basic
|
||||||
TABLE Contacts
|
TABLE Contacts ON maria
|
||||||
Id number key
|
Id number key
|
||||||
Nome string(150)
|
Nome string(150)
|
||||||
Email string(255)
|
Email string(255)
|
||||||
|
|
@ -82,7 +82,7 @@ END TABLE
|
||||||
### Table with Multiple Field Types
|
### Table with Multiple Field Types
|
||||||
|
|
||||||
```basic
|
```basic
|
||||||
TABLE Produtos
|
TABLE Produtos ON maria
|
||||||
Id number key
|
Id number key
|
||||||
Nome string(150)
|
Nome string(150)
|
||||||
Sku string(20)
|
Sku string(20)
|
||||||
|
|
@ -98,7 +98,7 @@ END TABLE
|
||||||
### Table with Foreign Key References
|
### Table with Foreign Key References
|
||||||
|
|
||||||
```basic
|
```basic
|
||||||
TABLE Pedidos
|
TABLE Pedidos ON maria
|
||||||
Id number key
|
Id number key
|
||||||
Numero integer
|
Numero integer
|
||||||
Data date
|
Data date
|
||||||
|
|
@ -108,7 +108,7 @@ TABLE Pedidos
|
||||||
Vendedor_id number
|
Vendedor_id number
|
||||||
END TABLE
|
END TABLE
|
||||||
|
|
||||||
TABLE PedidosItem
|
TABLE PedidosItem ON maria
|
||||||
Id number key
|
Id number key
|
||||||
Pedido_id number
|
Pedido_id number
|
||||||
Produto_id number
|
Produto_id number
|
||||||
|
|
@ -122,7 +122,7 @@ END TABLE
|
||||||
|
|
||||||
```basic
|
```basic
|
||||||
' Contact management tables
|
' Contact management tables
|
||||||
TABLE Contatos
|
TABLE Contatos ON maria
|
||||||
Id number key
|
Id number key
|
||||||
Nome string(150)
|
Nome string(150)
|
||||||
Codigo string(50)
|
Codigo string(50)
|
||||||
|
|
@ -142,7 +142,7 @@ TABLE Contatos
|
||||||
END TABLE
|
END TABLE
|
||||||
|
|
||||||
' Payment methods
|
' Payment methods
|
||||||
TABLE FormaDePagamento
|
TABLE FormaDePagamento ON maria
|
||||||
Id number key
|
Id number key
|
||||||
Descricao string(255)
|
Descricao string(255)
|
||||||
TipoPagamento integer
|
TipoPagamento integer
|
||||||
|
|
@ -153,7 +153,7 @@ TABLE FormaDePagamento
|
||||||
END TABLE
|
END TABLE
|
||||||
|
|
||||||
' Accounts receivable
|
' Accounts receivable
|
||||||
TABLE ContasAReceber
|
TABLE ContasAReceber ON maria
|
||||||
Id number key
|
Id number key
|
||||||
Situacao integer
|
Situacao integer
|
||||||
Vencimento date
|
Vencimento date
|
||||||
|
|
|
||||||
|
|
@ -2,96 +2,60 @@
|
||||||
|
|
||||||
**Syntax**
|
**Syntax**
|
||||||
|
|
||||||
```basic
|
```
|
||||||
USE WEBSITE "https://example.com"
|
USE WEBSITE "https://example.com"
|
||||||
|
|
||||||
USE WEBSITE "https://example.com" REFRESH "1d"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
- `"url"` – A valid HTTP or HTTPS URL pointing to a website that should be made available in the conversation context.
|
- `"url"` – A valid HTTP or HTTPS URL pointing to a website that should be made available in the conversation context.
|
||||||
- `"refresh"` – (Optional) How often to recrawl the website. Supports: `"1d"` (1 day), `"1w"` (1 week), `"1m"` (1 month), `"1y"` (1 year). Defaults to `"1m"`.
|
|
||||||
|
|
||||||
**Description**
|
**Description**
|
||||||
|
|
||||||
`USE WEBSITE` operates in two distinct modes:
|
`USE WEBSITE` operates in two distinct modes:
|
||||||
|
|
||||||
1. **Preprocessing Mode** (Script Compilation): When found in a BASIC script during compilation, it registers the website for background crawling. The crawler service will fetch, extract, and index the website's content into a vector database collection. The crawl happens immediately on first compile, then recurs based on the REFRESH interval.
|
1. **Preprocessing Mode** (Script Compilation): When found in a BASIC script during compilation, it registers the website for background crawling. The crawler service will fetch, extract, and index the website's content into a vector database collection. This ensures the website content is ready before any conversation starts.
|
||||||
|
|
||||||
2. **Runtime Mode** (Conversation Execution): During a conversation, `USE WEBSITE` associates an already-crawled website collection with the current session, making it available for queries via `FIND` or `LLM` calls. This behaves similarly to `USE KB` - it's a session-scoped association.
|
2. **Runtime Mode** (Conversation Execution): During a conversation, `USE WEBSITE` associates an already-crawled website collection with the current session, making it available for queries via `FIND` or `LLM` calls. This behaves similarly to `USE KB` - it's a session-scoped association.
|
||||||
|
|
||||||
If a website hasn't been registered during preprocessing, the runtime execution will auto-register it for crawling.
|
If a website hasn't been registered during preprocessing, the runtime execution will fail with an appropriate error message.
|
||||||
|
|
||||||
**Refresh Interval Behavior**
|
**Example**
|
||||||
|
|
||||||
- **Smart Interval Selection**: If the same URL is registered multiple times with different REFRESH intervals, the **shortest interval** is always used
|
|
||||||
- **Default**: If no REFRESH is specified, defaults to `"1m"` (1 month)
|
|
||||||
- **Formats Supported**:
|
|
||||||
- `"1d"` = 1 day
|
|
||||||
- `"1w"` = 1 week
|
|
||||||
- `"1m"` = 1 month (default)
|
|
||||||
- `"1y"` = 1 year
|
|
||||||
- Custom: `"3d"`, `"2w"`, `"6m"`, etc.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
Basic usage with default 1-month refresh:
|
|
||||||
```basic
|
```basic
|
||||||
|
' In script preprocessing, this registers the website for crawling
|
||||||
USE WEBSITE "https://docs.example.com"
|
USE WEBSITE "https://docs.example.com"
|
||||||
```
|
|
||||||
|
|
||||||
High-frequency website (daily refresh):
|
' During conversation, this makes the crawled content available
|
||||||
```basic
|
USE WEBSITE "https://docs.example.com"
|
||||||
USE WEBSITE "https://news.example.com" REFRESH "1d"
|
FIND "deployment procedures"
|
||||||
```
|
TALK "I found information about deployment procedures in the documentation."
|
||||||
|
|
||||||
Stable documentation (monthly refresh):
|
|
||||||
```basic
|
|
||||||
USE WEBSITE "https://api.example.com/docs" REFRESH "1m"
|
|
||||||
```
|
|
||||||
|
|
||||||
Multiple registrations - shortest interval wins:
|
|
||||||
```basic
|
|
||||||
USE WEBSITE "https://example.com" REFRESH "1w"
|
|
||||||
USE WEBSITE "https://example.com" REFRESH "1d"
|
|
||||||
' Final refresh interval: 1d (shortest)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Runtime Example**
|
|
||||||
|
|
||||||
```basic
|
|
||||||
USE WEBSITE "https://company.com/policies" REFRESH "1w"
|
|
||||||
question = HEAR "What would you like to know about our policies?"
|
|
||||||
FIND question
|
|
||||||
answer = LLM "Based on the search results, provide a clear answer"
|
|
||||||
TALK answer
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Preprocessing Behavior**
|
**Preprocessing Behavior**
|
||||||
|
|
||||||
When the script is compiled:
|
When the script is compiled:
|
||||||
- The URL is validated
|
- The URL is validated
|
||||||
- The website is registered in the `website_crawls` table with the specified refresh policy
|
- The website is registered in the `website_crawls` table
|
||||||
- The crawler service immediately starts crawling the website
|
- The crawler service picks it up and indexes the content
|
||||||
- Subsequent crawls are scheduled based on the REFRESH interval
|
|
||||||
- Status can be: pending (0), crawled (1), or failed (2)
|
- Status can be: pending (0), crawled (1), or failed (2)
|
||||||
|
|
||||||
**Runtime Behavior**
|
**Runtime Behavior**
|
||||||
|
|
||||||
When executed in a conversation:
|
When executed in a conversation:
|
||||||
- Checks if the website has been registered and crawled
|
- Checks if the website has been crawled
|
||||||
- If not registered, auto-registers with default 1-month refresh
|
|
||||||
- Associates the website collection with the current session
|
- Associates the website collection with the current session
|
||||||
- Makes the content searchable via `FIND` and available to `LLM`
|
- Makes the content searchable via `FIND` and available to `LLM`
|
||||||
|
|
||||||
**Database Schema**
|
**With LLM Integration**
|
||||||
|
|
||||||
The `website_crawls` table stores:
|
```basic
|
||||||
- `refresh_policy` - User-configured refresh interval (e.g., "1d", "1w", "1m")
|
USE WEBSITE "https://company.com/policies"
|
||||||
- `expires_policy` - Internal representation in days
|
question = HEAR "What would you like to know about our policies?"
|
||||||
- `next_crawl` - Timestamp for next scheduled crawl
|
FIND question
|
||||||
- `crawl_status` - 0=pending, 1=success, 2=processing, 3=error
|
answer = LLM "Based on the search results, provide a clear answer"
|
||||||
|
TALK answer
|
||||||
|
```
|
||||||
|
|
||||||
**Related Keywords**
|
**Related Keywords**
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ These conversations show how the authentication template works in real-world sce
|
||||||
|
|
||||||
<div class="wa-message user">
|
<div class="wa-message user">
|
||||||
<div class="wa-bubble">
|
<div class="wa-bubble">
|
||||||
<p>santos</p>
|
<p>maria.santos</p>
|
||||||
<div class="wa-time">09:00 <span class="wa-status read"></span></div>
|
<div class="wa-time">09:00 <span class="wa-status read"></span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -430,4 +430,4 @@ END SWITCH
|
||||||
.wa-header-info{flex:1}
|
.wa-header-info{flex:1}
|
||||||
.wa-header-name{font-weight:600;font-size:16px}
|
.wa-header-name{font-weight:600;font-size:16px}
|
||||||
.wa-header-status{font-size:12px;opacity:.8}
|
.wa-header-status{font-size:12px;opacity:.8}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -237,7 +237,7 @@ These conversations show how the HR Employees template works in real-world scena
|
||||||
<p>✅ <strong>Employee Added Successfully!</strong></p>
|
<p>✅ <strong>Employee Added Successfully!</strong></p>
|
||||||
<p></p>
|
<p></p>
|
||||||
<p>🆔 Employee ID: EMP-2025-0147</p>
|
<p>🆔 Employee ID: EMP-2025-0147</p>
|
||||||
<p>📧 Email: santos@company.com</p>
|
<p>📧 Email: maria.santos@company.com</p>
|
||||||
<p>📞 Extension: 4587</p>
|
<p>📞 Extension: 4587</p>
|
||||||
<p></p>
|
<p></p>
|
||||||
<p>📧 Welcome email sent to Maria</p>
|
<p>📧 Welcome email sent to Maria</p>
|
||||||
|
|
@ -692,4 +692,4 @@ The employee management system includes several security features:
|
||||||
.wa-header-info{flex:1}
|
.wa-header-info{flex:1}
|
||||||
.wa-header-name{font-weight:600;font-size:16px}
|
.wa-header-name{font-weight:600;font-size:16px}
|
||||||
.wa-header-status{font-size:12px;opacity:.8}
|
.wa-header-status{font-size:12px;opacity:.8}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -139,7 +139,7 @@ These conversations show how the enrollment template works in real-world scenari
|
||||||
|
|
||||||
<div class="wa-message user">
|
<div class="wa-message user">
|
||||||
<div class="wa-bubble">
|
<div class="wa-bubble">
|
||||||
<p>santos@email.com</p>
|
<p>maria.santos@email.com</p>
|
||||||
<div class="wa-time">10:32 <span class="wa-status read"></span></div>
|
<div class="wa-time">10:32 <span class="wa-status read"></span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -177,7 +177,7 @@ These conversations show how the enrollment template works in real-world scenari
|
||||||
<p>Please confirm your details:</p>
|
<p>Please confirm your details:</p>
|
||||||
<p><strong>Name:</strong> Maria Santos Silva</p>
|
<p><strong>Name:</strong> Maria Santos Silva</p>
|
||||||
<p><strong>Birth Date:</strong> 15/03/1990</p>
|
<p><strong>Birth Date:</strong> 15/03/1990</p>
|
||||||
<p><strong>Email:</strong> santos@email.com</p>
|
<p><strong>Email:</strong> maria.santos@email.com</p>
|
||||||
<p><strong>Personal ID:</strong> 12345678901</p>
|
<p><strong>Personal ID:</strong> 12345678901</p>
|
||||||
<p><strong>Address:</strong> Rua das Palmeiras, 456 - São Paulo, SP</p>
|
<p><strong>Address:</strong> Rua das Palmeiras, 456 - São Paulo, SP</p>
|
||||||
<div class="wa-time">10:33</div>
|
<div class="wa-time">10:33</div>
|
||||||
|
|
@ -397,4 +397,4 @@ SEND MAIL email, "Welcome!", "Your registration is complete, " + name
|
||||||
.wa-header-info{flex:1}
|
.wa-header-info{flex:1}
|
||||||
.wa-header-name{font-weight:600;font-size:16px}
|
.wa-header-name{font-weight:600;font-size:16px}
|
||||||
.wa-header-status{font-size:12px;opacity:.8}
|
.wa-header-status{font-size:12px;opacity:.8}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -7,36 +7,10 @@ This guide covers building botserver from source, including dependencies, featur
|
||||||
### System Requirements
|
### System Requirements
|
||||||
|
|
||||||
- **Operating System**: Linux, macOS, or Windows
|
- **Operating System**: Linux, macOS, or Windows
|
||||||
- **Rust**: 1.90 or later (2021 edition)
|
- **Rust**: 1.70 or later (2021 edition)
|
||||||
- **Memory**: 4GB RAM minimum (8GB recommended)
|
- **Memory**: 4GB RAM minimum (8GB recommended)
|
||||||
- **Disk Space**: 8GB for development environment
|
- **Disk Space**: 8GB for development environment
|
||||||
|
|
||||||
### Install Git
|
|
||||||
|
|
||||||
Git is required to clone the repository and manage submodules.
|
|
||||||
|
|
||||||
#### Linux
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install git
|
|
||||||
```
|
|
||||||
|
|
||||||
#### macOS
|
|
||||||
|
|
||||||
```bash
|
|
||||||
brew install git
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Windows
|
|
||||||
|
|
||||||
Download and install from: https://git-scm.com/download/win
|
|
||||||
|
|
||||||
Or use winget:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
winget install Git.Git
|
|
||||||
```
|
|
||||||
|
|
||||||
### Install Rust
|
### Install Rust
|
||||||
|
|
||||||
If you don't have Rust installed:
|
If you don't have Rust installed:
|
||||||
|
|
@ -57,146 +31,49 @@ cargo --version
|
||||||
|
|
||||||
#### Linux (Ubuntu/Debian)
|
#### Linux (Ubuntu/Debian)
|
||||||
|
|
||||||
**Critical: Install clang linker first** (fixes "linker `clang` not found" error):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y \
|
sudo apt install -y \
|
||||||
clang \
|
|
||||||
lld \
|
|
||||||
build-essential \
|
build-essential \
|
||||||
pkg-config \
|
pkg-config \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
libpq-dev \
|
libpq-dev \
|
||||||
cmake \
|
cmake
|
||||||
git
|
|
||||||
```
|
|
||||||
|
|
||||||
Configure Rust to use clang as the linker:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p ~/.cargo
|
|
||||||
cat >> ~/.cargo/config.toml << EOF
|
|
||||||
[target.x86_64-unknown-linux-gnu]
|
|
||||||
linker = "clang"
|
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
|
|
||||||
EOF
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Linux (Fedora/RHEL)
|
#### Linux (Fedora/RHEL)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo dnf install -y \
|
sudo dnf install -y \
|
||||||
clang \
|
|
||||||
lld \
|
|
||||||
gcc \
|
gcc \
|
||||||
gcc-c++ \
|
gcc-c++ \
|
||||||
make \
|
make \
|
||||||
pkg-config \
|
pkg-config \
|
||||||
openssl-devel \
|
openssl-devel \
|
||||||
postgresql-devel \
|
postgresql-devel \
|
||||||
cmake \
|
cmake
|
||||||
git
|
|
||||||
```
|
|
||||||
|
|
||||||
Configure Rust to use clang as the linker:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p ~/.cargo
|
|
||||||
cat >> ~/.cargo/config.toml << EOF
|
|
||||||
[target.x86_64-unknown-linux-gnu]
|
|
||||||
linker = "clang"
|
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
|
|
||||||
EOF
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### macOS
|
#### macOS
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install postgresql openssl cmake git
|
brew install postgresql openssl cmake
|
||||||
xcode-select --install
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Windows
|
#### Windows
|
||||||
|
|
||||||
Install Visual Studio Build Tools with C++ support from:
|
Install Visual Studio Build Tools with C++ support, then:
|
||||||
https://visualstudio.microsoft.com/downloads/
|
|
||||||
|
|
||||||
Select "Desktop development with C++" workload during installation.
|
```powershell
|
||||||
|
# Install PostgreSQL (for libpq)
|
||||||
Then install PostgreSQL manually from:
|
choco install postgresql
|
||||||
https://www.postgresql.org/download/windows/
|
```
|
||||||
|
|
||||||
## Clone Repository
|
## Clone Repository
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone --recursive https://github.com/GeneralBots/gb.git
|
git clone https://github.com/GeneralBots/botserver.git
|
||||||
cd gb
|
cd botserver
|
||||||
```
|
|
||||||
|
|
||||||
If you cloned without `--recursive`, initialize submodules:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git submodule update --init --recursive
|
|
||||||
```
|
|
||||||
|
|
||||||
## Build Cache with sccache
|
|
||||||
|
|
||||||
sccache (Shared Compilation Cache) dramatically speeds up rebuilds by caching compilation artifacts. Highly recommended for development.
|
|
||||||
|
|
||||||
### Install sccache
|
|
||||||
|
|
||||||
#### Linux
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo install sccache
|
|
||||||
```
|
|
||||||
|
|
||||||
#### macOS
|
|
||||||
|
|
||||||
```bash
|
|
||||||
brew install sccache
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Windows
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cargo install sccache
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configure Cargo to Use sccache
|
|
||||||
|
|
||||||
Add to `~/.cargo/config.toml`:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[build]
|
|
||||||
compiler = "sccache"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Verify sccache is Working
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export RUSTC_WRAPPER=sccache
|
|
||||||
cargo build --release
|
|
||||||
sccache --show-stats
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected output shows cache hits/misses:
|
|
||||||
|
|
||||||
```
|
|
||||||
Compile requests 45
|
|
||||||
Compile requests executed 12
|
|
||||||
Cache hits 8
|
|
||||||
Cache misses 4
|
|
||||||
Cache hit rate 66.67%
|
|
||||||
```
|
|
||||||
|
|
||||||
### Clear sccache
|
|
||||||
|
|
||||||
If you need to clear the cache:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sccache --zero-stats
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build Configurations
|
## Build Configurations
|
||||||
|
|
@ -379,27 +256,6 @@ cross build --release --target x86_64-pc-windows-gnu
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Linker Errors (Linux)
|
|
||||||
|
|
||||||
**Error: `linker 'clang' not found`**
|
|
||||||
|
|
||||||
This occurs when clang/lld is not installed:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install clang lld
|
|
||||||
```
|
|
||||||
|
|
||||||
Then configure Rust to use clang:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p ~/.cargo
|
|
||||||
cat >> ~/.cargo/config.toml << EOF
|
|
||||||
[target.x86_64-unknown-linux-gnu]
|
|
||||||
linker = "clang"
|
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
### OpenSSL Errors
|
### OpenSSL Errors
|
||||||
|
|
||||||
If you encounter OpenSSL linking errors:
|
If you encounter OpenSSL linking errors:
|
||||||
|
|
@ -416,10 +272,10 @@ cargo build --release
|
||||||
```
|
```
|
||||||
|
|
||||||
**Windows:**
|
**Windows:**
|
||||||
```powershell
|
```bash
|
||||||
# Use vcpkg
|
# Use vcpkg
|
||||||
vcpkg install openssl:x64-windows
|
vcpkg install openssl:x64-windows
|
||||||
$env:OPENSSL_DIR="C:\vcpkg\installed\x64-windows"
|
set OPENSSL_DIR=C:\vcpkg\installed\x64-windows
|
||||||
cargo build --release
|
cargo build --release
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -439,22 +295,14 @@ export PQ_LIB_DIR=$(brew --prefix postgresql)/lib
|
||||||
```
|
```
|
||||||
|
|
||||||
**Windows:**
|
**Windows:**
|
||||||
```powershell
|
```bash
|
||||||
# Ensure PostgreSQL is in PATH
|
# Ensure PostgreSQL is in PATH
|
||||||
$env:PQ_LIB_DIR="C:\Program Files\PostgreSQL\15\lib"
|
set PQ_LIB_DIR=C:\Program Files\PostgreSQL\15\lib
|
||||||
```
|
```
|
||||||
|
|
||||||
### Out of Memory During Build
|
### Out of Memory During Build
|
||||||
|
|
||||||
Use sccache to cache compilations:
|
Reduce parallel jobs:
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo install sccache
|
|
||||||
export RUSTC_WRAPPER=sccache
|
|
||||||
cargo build --release
|
|
||||||
```
|
|
||||||
|
|
||||||
Or reduce parallel jobs:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo build --release -j 2
|
cargo build --release -j 2
|
||||||
|
|
@ -483,118 +331,6 @@ xcode-select --install
|
||||||
**Windows:**
|
**Windows:**
|
||||||
Install Visual Studio Build Tools with C++ support.
|
Install Visual Studio Build Tools with C++ support.
|
||||||
|
|
||||||
## Common Build Errors
|
|
||||||
|
|
||||||
### Error: `linker 'clang' not found`
|
|
||||||
|
|
||||||
**Cause:** The C/C++ toolchain is missing or not configured.
|
|
||||||
|
|
||||||
**Solution (Linux):**
|
|
||||||
|
|
||||||
1. Install clang and lld:
|
|
||||||
```bash
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install -y clang lld build-essential
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Configure Rust to use clang:
|
|
||||||
```bash
|
|
||||||
mkdir -p ~/.cargo
|
|
||||||
cat > ~/.cargo/config.toml << 'EOF'
|
|
||||||
[build]
|
|
||||||
rustflags = ["-C", "linker=clang", "-C", "link-arg=-fuse-ld=lld"]
|
|
||||||
|
|
||||||
[target.x86_64-unknown-linux-gnu]
|
|
||||||
linker = "clang"
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Clean and rebuild:
|
|
||||||
```bash
|
|
||||||
cargo clean
|
|
||||||
cargo build --release
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution (macOS):**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
xcode-select --install
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution (Windows):**
|
|
||||||
|
|
||||||
Install Visual Studio Build Tools with "Desktop development with C++" workload.
|
|
||||||
|
|
||||||
### Error: `could not find native library pq`
|
|
||||||
|
|
||||||
**Cause:** PostgreSQL development libraries are missing.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
|
|
||||||
**Linux:**
|
|
||||||
```bash
|
|
||||||
sudo apt install libpq-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
**macOS:**
|
|
||||||
```bash
|
|
||||||
brew install postgresql
|
|
||||||
export PQ_LIB_DIR=$(brew --prefix postgresql)/lib
|
|
||||||
```
|
|
||||||
|
|
||||||
**Windows:** Install PostgreSQL from postgresql.org
|
|
||||||
|
|
||||||
### Error: `openssl-sys` build failures
|
|
||||||
|
|
||||||
**Cause:** OpenSSL headers are missing.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
|
|
||||||
**Linux:**
|
|
||||||
```bash
|
|
||||||
sudo apt install libssl-dev pkg-config
|
|
||||||
```
|
|
||||||
|
|
||||||
**macOS:**
|
|
||||||
```bash
|
|
||||||
brew install openssl
|
|
||||||
export OPENSSL_DIR=$(brew --prefix openssl)
|
|
||||||
export OPENSSL_LIB_DIR=$(brew --prefix openssl)/lib
|
|
||||||
export OPENSSL_INCLUDE_DIR=$(brew --prefix openssl)/include
|
|
||||||
```
|
|
||||||
|
|
||||||
### Error: Out of memory during build
|
|
||||||
|
|
||||||
**Cause:** Too many parallel compilation jobs.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
|
|
||||||
Reduce parallel jobs:
|
|
||||||
```bash
|
|
||||||
CARGO_BUILD_JOBS=2 cargo build --release
|
|
||||||
```
|
|
||||||
|
|
||||||
Or limit memory:
|
|
||||||
```bash
|
|
||||||
ulimit -v 4000000 # Limit to 4GB
|
|
||||||
cargo build --release
|
|
||||||
```
|
|
||||||
|
|
||||||
### Error: Submodule references not found
|
|
||||||
|
|
||||||
**Cause:** Submodules not initialized.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git submodule update --init --recursive
|
|
||||||
```
|
|
||||||
|
|
||||||
Or re-clone with submodules:
|
|
||||||
```bash
|
|
||||||
git clone --recursive https://github.com/GeneralBots/gb.git
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verify Build
|
## Verify Build
|
||||||
|
|
||||||
After building, verify the binary works:
|
After building, verify the binary works:
|
||||||
|
|
@ -603,7 +339,7 @@ After building, verify the binary works:
|
||||||
./target/release/botserver --version
|
./target/release/botserver --version
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected output: `botserver 6.2.0` or similar.
|
Expected output: `botserver 6.0.8` or similar.
|
||||||
|
|
||||||
## Development Builds
|
## Development Builds
|
||||||
|
|
||||||
|
|
@ -687,16 +423,6 @@ cargo audit
|
||||||
|
|
||||||
This should be run regularly during development to ensure dependencies are secure.
|
This should be run regularly during development to ensure dependencies are secure.
|
||||||
|
|
||||||
### Quick Build Check
|
|
||||||
|
|
||||||
Check if everything compiles without building:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo check --all-features
|
|
||||||
```
|
|
||||||
|
|
||||||
This is much faster than a full build and catches most errors.
|
|
||||||
|
|
||||||
## Build Artifacts
|
## Build Artifacts
|
||||||
|
|
||||||
After a successful release build, you'll have:
|
After a successful release build, you'll have:
|
||||||
|
|
@ -723,6 +449,17 @@ upx --best --lzma target/release/botserver
|
||||||
|
|
||||||
Note: UPX may cause issues with some systems. Test thoroughly.
|
Note: UPX may cause issues with some systems. Test thoroughly.
|
||||||
|
|
||||||
|
## Incremental Compilation
|
||||||
|
|
||||||
|
For faster development builds:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export CARGO_INCREMENTAL=1
|
||||||
|
cargo build
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: This is enabled by default for debug builds.
|
||||||
|
|
||||||
## Clean Build
|
## Clean Build
|
||||||
|
|
||||||
Remove all build artifacts:
|
Remove all build artifacts:
|
||||||
|
|
@ -731,44 +468,7 @@ Remove all build artifacts:
|
||||||
cargo clean
|
cargo clean
|
||||||
```
|
```
|
||||||
|
|
||||||
## CI/CD Builds
|
## LXC Build
|
||||||
|
|
||||||
For automated builds in CI/CD pipelines:
|
|
||||||
|
|
||||||
### GitHub Actions
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
name: Build
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
|
|
||||||
- name: Install Rust
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install -y clang lld build-essential pkg-config libssl-dev libpq-dev cmake
|
|
||||||
|
|
||||||
- name: Cache sccache
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: ~/.cache/sccache
|
|
||||||
key: ${{ runner.os }}-sccache-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: cargo build --release --all-features
|
|
||||||
```
|
|
||||||
|
|
||||||
### LXC Build
|
|
||||||
|
|
||||||
Build inside LXC container:
|
Build inside LXC container:
|
||||||
|
|
||||||
|
|
@ -788,14 +488,14 @@ lxc-start -n botserver-build
|
||||||
# Install build dependencies
|
# Install build dependencies
|
||||||
lxc-attach -n botserver-build -- bash -c "
|
lxc-attach -n botserver-build -- bash -c "
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y clang lld build-essential pkg-config libssl-dev libpq-dev cmake curl git
|
apt-get install -y build-essential pkg-config libssl-dev libpq-dev cmake curl git
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||||
source \$HOME/.cargo/env
|
source \$HOME/.cargo/env
|
||||||
"
|
"
|
||||||
|
|
||||||
# Build botserver
|
# Build botserver
|
||||||
lxc-attach -n botserver-build -- bash -c "
|
lxc-attach -n botserver-build -- bash -c "
|
||||||
git clone --recursive https://github.com/GeneralBots/gb.git /build
|
git clone https://github.com/GeneralBots/botserver /build
|
||||||
cd /build
|
cd /build
|
||||||
source \$HOME/.cargo/env
|
source \$HOME/.cargo/env
|
||||||
cargo build --release --no-default-features
|
cargo build --release --no-default-features
|
||||||
|
|
@ -820,27 +520,6 @@ Or create a symlink:
|
||||||
ln -s $(pwd)/target/release/botserver ~/.local/bin/botserver
|
ln -s $(pwd)/target/release/botserver ~/.local/bin/botserver
|
||||||
```
|
```
|
||||||
|
|
||||||
Verify installation:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
botserver --version
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected output: `botserver 6.2.0` or similar.
|
|
||||||
|
|
||||||
## Quick Reference
|
|
||||||
|
|
||||||
| Command | Purpose |
|
|
||||||
|---------|---------|
|
|
||||||
| `cargo build --release` | Optimized production build |
|
|
||||||
| `cargo build --release -j 2` | Build with limited parallelism |
|
|
||||||
| `cargo check` | Fast syntax/type checking |
|
|
||||||
| `cargo test` | Run all tests |
|
|
||||||
| `cargo clippy` | Lint code |
|
|
||||||
| `cargo clean` | Remove build artifacts |
|
|
||||||
| `CARGO_BUILD_JOBS=2 cargo build` | Limit build jobs |
|
|
||||||
| `RUSTC_WRAPPER=sccache cargo build` | Use compilation cache |
|
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
After building:
|
After building:
|
||||||
|
|
|
||||||
|
|
@ -1,275 +1 @@
|
||||||
# Channel Integrations
|
# Channel Integrations
|
||||||
|
|
||||||
This guide covers integrating messaging channels with General Bots, focusing on WhatsApp Business API integration using Twilio-purchased phone numbers.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
General Bots supports multiple messaging channels through a unified API. This section focuses on WhatsApp Business API, the most widely used business messaging platform globally.
|
|
||||||
|
|
||||||
## Supported Channels
|
|
||||||
|
|
||||||
| Channel | Status | Config Keys |
|
|
||||||
|---------|--------|-------------|
|
|
||||||
| WhatsApp | ✅ Production Ready | `whatsapp-api-key`, `whatsapp-phone-number-id` |
|
|
||||||
| Twilio SMS | ✅ Production Ready | `twilio-account-sid`, `twilio-auth-token` |
|
|
||||||
| Instagram | ✅ Production Ready | `instagram-access-token`, `instagram-page-id` |
|
|
||||||
| Microsoft Teams | ✅ Production Ready | `teams-app-id`, `teams-app-password` |
|
|
||||||
|
|
||||||
## WhatsApp Business Integration
|
|
||||||
|
|
||||||
The most popular channel for business messaging. Complete integration guide: [WhatsApp Quick Start](./whatsapp-quick-start.md)
|
|
||||||
|
|
||||||
### Quick Setup (5 minutes)
|
|
||||||
|
|
||||||
1. **Purchase a phone number from Twilio**
|
|
||||||
```bash
|
|
||||||
# Twilio Console > Phone Numbers > Buy a Number
|
|
||||||
# Select: Voice capability (required for verification)
|
|
||||||
# Example: +553322980098
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Create Meta App with WhatsApp**
|
|
||||||
```bash
|
|
||||||
# https://developers.facebook.com/apps/
|
|
||||||
# Create App > Business > Add WhatsApp product
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Configure credentials in `config.csv`**
|
|
||||||
```csv
|
|
||||||
whatsapp-enabled,true
|
|
||||||
whatsapp-api-key,EAAQdlso6aM8BOwlhc3yM6bbJkGyibQPGJd87zFDHtfaFoJDJPohMl2c5nXs4yYuuHwoXJWx0rQKo0VXgTwThPYzqLEZArOZBhCWPBUpq7YlkEJXFAgB6ZAb3eoUzZAMgNZCZA1sg11rT2G8e1ZAgzpRVRffU4jmMChc7ybcyIwbtGOPKZAXKcNoMRfUwssoLhDWr
|
|
||||||
whatsapp-phone-number-id,1158433381968079
|
|
||||||
whatsapp-business-account-id,390727550789228
|
|
||||||
whatsapp-webhook-verify-token,4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY
|
|
||||||
whatsapp-application-id,323250907549153
|
|
||||||
```
|
|
||||||
|
|
||||||
### BASIC Keywords for WhatsApp
|
|
||||||
|
|
||||||
```basic
|
|
||||||
REM Send a message
|
|
||||||
SEND WHATSAPP TO "+5511999999999" WITH "Hello from General Bots!"
|
|
||||||
|
|
||||||
REM Handle incoming messages
|
|
||||||
ON WHATSAPP MESSAGE RECEIVED
|
|
||||||
LET SENDER$ = GET WHATSAPP SENDER NUMBER
|
|
||||||
LET MESSAGE$ = GET WHATSAPP MESSAGE BODY
|
|
||||||
|
|
||||||
REM Echo message back
|
|
||||||
SEND WHATSAPP TO SENDER$ WITH "You said: " + MESSAGE$
|
|
||||||
END ON
|
|
||||||
```
|
|
||||||
|
|
||||||
### Credential Reference
|
|
||||||
|
|
||||||
| Credential | Format | Example | Purpose |
|
|
||||||
|------------|--------|---------|---------|
|
|
||||||
| Access Token | `EAAQ...` | `EAAQdlso6aM8BOwl...` | API authentication |
|
|
||||||
| Phone Number ID | 16 digits | `1158433381968079` | Message sending endpoint |
|
|
||||||
| WABA ID | 15 digits | `390727550789228` | Business account identifier |
|
|
||||||
| Verify Token | Custom string | `4qIogZadggQ.BEoMeci...` | Webhook security |
|
|
||||||
| Application ID | 15 digits | `323250907549153` | App identifier |
|
|
||||||
|
|
||||||
### Phone Number Verification
|
|
||||||
|
|
||||||
Twilio numbers require **voice call verification** (not SMS):
|
|
||||||
|
|
||||||
1. **Configure Twilio webhook** to capture verification calls
|
|
||||||
```xml
|
|
||||||
<!-- TwiML for voice handling -->
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Response>
|
|
||||||
<Gather action="https://twimlets.com/voicemail?Email=your@email.com">
|
|
||||||
<Say voice="alice">Please enter your verification code.</Say>
|
|
||||||
</Gather>
|
|
||||||
</Response>
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **In Meta Business Suite**: Select "Phone Call" verification method
|
|
||||||
3. **Enter the 6-digit code** received via email
|
|
||||||
4. **Verification complete** - number ready for WhatsApp
|
|
||||||
|
|
||||||
See: [Webhook Configuration Guide](./whatsapp-webhooks.md)
|
|
||||||
|
|
||||||
## Advanced Configuration
|
|
||||||
|
|
||||||
### Message Templates
|
|
||||||
|
|
||||||
For business-initiated messages outside the 24-hour window:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Send template message
|
|
||||||
POST https://graph.facebook.com/v18.0/1158433381968079/messages
|
|
||||||
{
|
|
||||||
"messaging_product": "whatsapp",
|
|
||||||
"to": "5511999999999",
|
|
||||||
"type": "template",
|
|
||||||
"template": {
|
|
||||||
"name": "hello_world",
|
|
||||||
"language": { "code": "pt_BR" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Rate Limiting
|
|
||||||
|
|
||||||
WhatsApp enforces rate limits per tier:
|
|
||||||
|
|
||||||
| Tier | Messages/Day | Messages/Second |
|
|
||||||
|------|--------------|-----------------|
|
|
||||||
| Tier 1 | 1,000 | 1 |
|
|
||||||
| Tier 2 | 10,000 | 5 |
|
|
||||||
| Tier 3 | 100,000 | 50 |
|
|
||||||
| Tier 4 | Unlimited | 1,000 |
|
|
||||||
|
|
||||||
Implement rate limiting in your bot:
|
|
||||||
|
|
||||||
```basic
|
|
||||||
REM Simple rate limiting
|
|
||||||
LET LAST_SENT = 0
|
|
||||||
SUB SEND WHATSAPP WITH LIMIT TO NUMBER$, MESSAGE$
|
|
||||||
LET NOW = TIMER
|
|
||||||
IF NOW - LAST_SENT < 1 THEN
|
|
||||||
WAIT 1 - (NOW - LAST_SENT)
|
|
||||||
END IF
|
|
||||||
SEND WHATSAPP TO NUMBER$ WITH MESSAGE$
|
|
||||||
LAST_SENT = TIMER
|
|
||||||
END SUB
|
|
||||||
```
|
|
||||||
|
|
||||||
### Webhook Security
|
|
||||||
|
|
||||||
Always verify webhook signatures:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Node.js signature verification
|
|
||||||
const crypto = require('crypto');
|
|
||||||
|
|
||||||
function verifySignature(payload, signature, appSecret) {
|
|
||||||
const expected = 'sha256=' +
|
|
||||||
crypto.createHmac('sha256', appSecret)
|
|
||||||
.update(payload)
|
|
||||||
.digest('hex');
|
|
||||||
return crypto.timingSafeEqual(
|
|
||||||
Buffer.from(signature),
|
|
||||||
Buffer.from(expected)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Complete Documentation
|
|
||||||
|
|
||||||
For detailed guides and examples:
|
|
||||||
|
|
||||||
- **[WhatsApp Quick Start Guide](./whatsapp-quick-start.md)** - 30-minute setup walkthrough
|
|
||||||
- **[Webhook Configuration](./whatsapp-webhooks.md)** - Detailed webhook setup for Twilio and Meta
|
|
||||||
- **[Code Examples](./whatsapp-examples.md)** - Examples in BASIC, Node.js, and Python
|
|
||||||
- **[Troubleshooting Guide](./whatsapp-troubleshooting.md)** - Common issues and solutions
|
|
||||||
- **[Quick Reference](./whatsapp-quick-reference.md)** - Commands, configs, and snippets
|
|
||||||
|
|
||||||
## Other Channels
|
|
||||||
|
|
||||||
### Twilio SMS
|
|
||||||
|
|
||||||
Simple SMS integration using Twilio:
|
|
||||||
|
|
||||||
```csv
|
|
||||||
# config.csv
|
|
||||||
twilio-account-sid,ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
||||||
twilio-auth-token,your_auth_token_here
|
|
||||||
twilio-from-number,+15551234567
|
|
||||||
```
|
|
||||||
|
|
||||||
```basic
|
|
||||||
REM Send SMS
|
|
||||||
SEND SMS TO "+5511999999999" WITH "Hello via SMS!"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Instagram Direct Messages
|
|
||||||
|
|
||||||
Connect Instagram messaging:
|
|
||||||
|
|
||||||
```csv
|
|
||||||
# config.csv
|
|
||||||
instagram-access-token,EAAxxxx...
|
|
||||||
instagram-page-id,123456789012345
|
|
||||||
```
|
|
||||||
|
|
||||||
```basic
|
|
||||||
REM Send Instagram DM
|
|
||||||
SEND INSTAGRAM TO "1234567890" WITH "Hello via Instagram!"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration Template
|
|
||||||
|
|
||||||
Complete channel configuration example:
|
|
||||||
|
|
||||||
```csv
|
|
||||||
# config.csv
|
|
||||||
|
|
||||||
# WhatsApp Business (Primary channel)
|
|
||||||
whatsapp-enabled,true
|
|
||||||
whatsapp-api-key,EAAQdlso6aM8BOwlhc3yM6bbJkGyibQPGJd87zFDHtfaFoJDJPohMl2c5nXs4yYuuHwoXJWx0rQKo0VXgTwThPYzqLEZArOZBhCWPBUpq7YlkEJXFAgB6ZAb3eoUzZAMgNZCZA1sg11rT2G8e1ZAgzpRVRffU4jmMChc7ybcyIwbtGOPKZAXKcNoMRfUwssoLhDWr
|
|
||||||
whatsapp-phone-number-id,1158433381968079
|
|
||||||
whatsapp-business-account-id,390727550789228
|
|
||||||
whatsapp-webhook-verify-token,4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY
|
|
||||||
|
|
||||||
# Twilio SMS (Backup channel)
|
|
||||||
twilio-enabled,false
|
|
||||||
twilio-account-sid,ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
||||||
twilio-auth-token,your_auth_token_here
|
|
||||||
twilio-from-number,+15551234567
|
|
||||||
|
|
||||||
# Instagram (Social channel)
|
|
||||||
instagram-enabled,false
|
|
||||||
instagram-access-token,EAAxxxx...
|
|
||||||
instagram-page-id,123456789012345
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
|
|
||||||
**Issue: Phone number verification fails**
|
|
||||||
- **Solution**: Ensure "Phone Call" verification is selected (not SMS)
|
|
||||||
- **Solution**: Verify Twilio webhook is configured correctly
|
|
||||||
- See: [Troubleshooting Guide](./whatsapp-troubleshooting.md)
|
|
||||||
|
|
||||||
**Issue: Messages not sending**
|
|
||||||
- **Solution**: Check access token validity
|
|
||||||
- **Solution**: Verify phone number format: `5511999999999` (no +, no spaces)
|
|
||||||
- **Solution**: Ensure webhook is subscribed to "messages" field
|
|
||||||
|
|
||||||
**Issue: Rate limit errors**
|
|
||||||
- **Solution**: Implement rate limiting in your bot
|
|
||||||
- **Solution**: Use message queues for bulk sending
|
|
||||||
- See: [Code Examples](./whatsapp-examples.md)
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
1. **Never hardcode credentials** - Always use `config.csv`
|
|
||||||
2. **Implement retry logic** - Handle API failures gracefully
|
|
||||||
3. **Monitor rate limits** - Respect platform limits
|
|
||||||
4. **Secure webhooks** - Verify all incoming requests
|
|
||||||
5. **Test thoroughly** - Use ngrok for local testing
|
|
||||||
6. **Log everything** - Track message delivery and errors
|
|
||||||
7. **Use templates** - Pre-approved templates for business-initiated messages
|
|
||||||
8. **Handle errors** - Provide user-friendly error messages
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
- **Documentation**: [Full guide](./whatsapp-quick-start.md)
|
|
||||||
- **Examples**: [Code samples](./whatsapp-examples.md)
|
|
||||||
- **Community**: [General Bots Discord](https://discord.gg/general-bots)
|
|
||||||
- **Meta Docs**: [WhatsApp Business API](https://developers.facebook.com/docs/whatsapp/)
|
|
||||||
- **Twilio Docs**: [Twilio WhatsApp](https://www.twilio.com/docs/whatsapp)
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. Complete the [Quick Start Guide](./whatsapp-quick-start.md)
|
|
||||||
2. Set up webhooks using [Webhook Configuration](./whatsapp-webhooks.md)
|
|
||||||
3. Explore [Code Examples](./whatsapp-examples.md) for your use case
|
|
||||||
4. Configure monitoring and error handling
|
|
||||||
5. Test with your team before launching to users
|
|
||||||
|
|
||||||
For configuration of other services (LLM providers, databases, etc.), see [Appendix B: External Services](./README.md).
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,678 +0,0 @@
|
||||||
# Quick Reference Guide
|
|
||||||
|
|
||||||
Essential commands, configurations, and code snippets for WhatsApp Business API integration with Twilio.
|
|
||||||
|
|
||||||
## Configuration Keys
|
|
||||||
|
|
||||||
### Required config.csv Entries
|
|
||||||
|
|
||||||
```csv
|
|
||||||
# Core Configuration
|
|
||||||
whatsapp-enabled,true
|
|
||||||
whatsapp-api-key,EAAQdlso6aM8BOwlhc3yM6bbJkGyibQPGJd87zFDHtfaFoJDJPohMl2c5nXs4yYuuHwoXJWx0rQKo0VXgTwThPYzqLEZArOZBhCWPBUpq7YlkEJXFAgB6ZAb3eoUzZAMgNZCZA1sg11rT2G8e1ZAgzpRVRffU4jmMChc7ybcyIwbtGOPKZAXKcNoMRfUwssoLhDWr
|
|
||||||
whatsapp-phone-number-id,1158433381968079
|
|
||||||
whatsapp-business-account-id,390727550789228
|
|
||||||
whatsapp-webhook-verify-token,4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY
|
|
||||||
whatsapp-application-id,323250907549153
|
|
||||||
|
|
||||||
# Optional: Advanced Settings
|
|
||||||
whatsapp-webhook-url,https://your-domain.com/webhooks/whatsapp
|
|
||||||
whatsapp-timeout,30000
|
|
||||||
whatsapp-retry-attempts,3
|
|
||||||
whatsapp-rate-limit,50
|
|
||||||
whatsapp-from-number,+553322980098
|
|
||||||
```
|
|
||||||
|
|
||||||
### Environment Variables
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Meta WhatsApp Configuration
|
|
||||||
export WHATSAPP_API_KEY="EAAQdlso6aM8BOwl..."
|
|
||||||
export WHATSAPP_PHONE_NUMBER_ID="1158433381968079"
|
|
||||||
export WHATSAPP_WABA_ID="390727550789228"
|
|
||||||
export WHATSAPP_VERIFY_TOKEN="4qIogZadggQ.BEoMeci..."
|
|
||||||
export WHATSAPP_APPLICATION_ID="323250907549153"
|
|
||||||
export WHATSAPP_APP_SECRET="your_app_secret_here"
|
|
||||||
|
|
||||||
# Twilio Configuration
|
|
||||||
export TWILIO_ACCOUNT_SID="ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
||||||
export TWILIO_AUTH_TOKEN="your_auth_token_here"
|
|
||||||
export TWILIO_PHONE_NUMBER="+553322980098"
|
|
||||||
|
|
||||||
# Webhook Configuration
|
|
||||||
export WEBHOOK_URL="https://your-domain.com/webhooks/whatsapp"
|
|
||||||
export WEBHOOK_PORT="3000"
|
|
||||||
```
|
|
||||||
|
|
||||||
## API Endpoints
|
|
||||||
|
|
||||||
### Meta WhatsApp API
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Base URL
|
|
||||||
https://graph.facebook.com/v18.0
|
|
||||||
|
|
||||||
# Send Message
|
|
||||||
POST /{phone-number-id}/messages
|
|
||||||
|
|
||||||
# Get Phone Number Info
|
|
||||||
GET /{phone-number-id}
|
|
||||||
|
|
||||||
# Mark as Read
|
|
||||||
POST /{message-id}
|
|
||||||
|
|
||||||
# Upload Media
|
|
||||||
POST /{phone-number-id}/media
|
|
||||||
|
|
||||||
# Get Message Templates
|
|
||||||
GET /{waba-id}/message_templates
|
|
||||||
```
|
|
||||||
|
|
||||||
### Twilio API
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Base URL
|
|
||||||
https://api.twilio.com/2010-04-01
|
|
||||||
|
|
||||||
# Incoming Call Webhook
|
|
||||||
POST /twilio/voice
|
|
||||||
|
|
||||||
# Gather Handler
|
|
||||||
POST /twilio/gather
|
|
||||||
|
|
||||||
# Get Call Logs
|
|
||||||
GET /Accounts/{AccountSid}/Calls.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common API Requests
|
|
||||||
|
|
||||||
### Send Text Message
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST \
|
|
||||||
'https://graph.facebook.com/v18.0/1158433381968079/messages' \
|
|
||||||
-H 'Authorization: Bearer EAAQdlso6aM8BOwl...' \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d '{
|
|
||||||
"messaging_product": "whatsapp",
|
|
||||||
"to": "5511999999999",
|
|
||||||
"type": "text",
|
|
||||||
"text": {
|
|
||||||
"body": "Hello from General Bots!"
|
|
||||||
}
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Send Image
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST \
|
|
||||||
'https://graph.facebook.com/v18.0/1158433381968079/messages' \
|
|
||||||
-H 'Authorization: Bearer EAAQdlso6aM8BOwl...' \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d '{
|
|
||||||
"messaging_product": "whatsapp",
|
|
||||||
"to": "5511999999999",
|
|
||||||
"type": "image",
|
|
||||||
"image": {
|
|
||||||
"link": "https://example.com/image.jpg",
|
|
||||||
"caption": "Check this out!"
|
|
||||||
}
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Send Template
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST \
|
|
||||||
'https://graph.facebook.com/v18.0/1158433381968079/messages' \
|
|
||||||
-H 'Authorization: Bearer EAAQdlso6aM8BOwl...' \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d '{
|
|
||||||
"messaging_product": "whatsapp",
|
|
||||||
"to": "5511999999999",
|
|
||||||
"type": "template",
|
|
||||||
"template": {
|
|
||||||
"name": "hello_world",
|
|
||||||
"language": {
|
|
||||||
"code": "en_US"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mark as Read
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST \
|
|
||||||
'https://graph.facebook.com/v18.0/wamid.HBgLNTE1OTk5OTk5OTk5FQIAERgSMzg1QTlCNkE2RTlFRTdFNDdF' \
|
|
||||||
-H 'Authorization: Bearer EAAQdlso6aM8BOwl...' \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d '{
|
|
||||||
"messaging_product": "whatsapp",
|
|
||||||
"status": "read"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
## BASIC Keywords
|
|
||||||
|
|
||||||
### Sending Messages
|
|
||||||
|
|
||||||
```basic
|
|
||||||
REM Send simple text
|
|
||||||
SEND WHATSAPP TO "+5511999999999" WITH "Hello!"
|
|
||||||
|
|
||||||
REM Send with formatting
|
|
||||||
SEND WHATSAPP TO "+5511999999999" WITH "*Bold* and _italics_"
|
|
||||||
|
|
||||||
REM Send location
|
|
||||||
SEND WHATSAPP TO "+5511999999999" WITH LOCATION AT "-23.5505,-46.6333"
|
|
||||||
|
|
||||||
REM Send image
|
|
||||||
SEND WHATSAPP TO "+5511999999999" WITH IMAGE FROM "https://example.com/img.jpg"
|
|
||||||
|
|
||||||
REM Send document
|
|
||||||
SEND WHATSAPP TO "+5511999999999" WITH DOCUMENT FROM "https://example.com/doc.pdf"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Receiving Messages
|
|
||||||
|
|
||||||
```basic
|
|
||||||
REM Handle incoming messages
|
|
||||||
ON WHATSAPP MESSAGE RECEIVED
|
|
||||||
LET SENDER$ = GET WHATSAPP SENDER NUMBER
|
|
||||||
LET MESSAGE$ = GET WHATSAPP MESSAGE BODY
|
|
||||||
LET ID$ = GET WHATSAPP MESSAGE ID
|
|
||||||
LET TIMESTAMP$ = GET WHATSAPP MESSAGE TIMESTAMP
|
|
||||||
|
|
||||||
LOG "From: " + SENDER$ + " Msg: " + MESSAGE$
|
|
||||||
|
|
||||||
REM Process message
|
|
||||||
PROCESS MESSAGE SENDER$, MESSAGE$
|
|
||||||
END ON
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Codes
|
|
||||||
|
|
||||||
### Common HTTP Status Codes
|
|
||||||
|
|
||||||
| Code | Meaning | Solution |
|
|
||||||
|------|---------|----------|
|
|
||||||
| 200 | Success | Request completed |
|
|
||||||
| 400 | Bad Request | Check request body |
|
|
||||||
| 401 | Unauthorized | Verify access token |
|
|
||||||
| 403 | Forbidden | Check permissions |
|
|
||||||
| 404 | Not Found | Verify phone number ID |
|
|
||||||
| 429 | Rate Limited | Implement backoff |
|
|
||||||
| 500 | Server Error | Retry later |
|
|
||||||
|
|
||||||
### WhatsApp-Specific Errors
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": {
|
|
||||||
"message": "Invalid parameter",
|
|
||||||
"type": "WhatsAppApiError",
|
|
||||||
"code": 130426,
|
|
||||||
"error_data": {
|
|
||||||
"details": "Phone number ID is required"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
| Error Code | Description | Solution |
|
|
||||||
|------------|-------------|----------|
|
|
||||||
| 130426 | Invalid parameter | Check phone number format |
|
|
||||||
| 130472 | Rate limit hit | Reduce message frequency |
|
|
||||||
| 131056 | 24-hour window | Use message templates |
|
|
||||||
| 131047 | Media upload failed | Check media URL |
|
|
||||||
| 131053 | Template not approved | Submit template for review |
|
|
||||||
|
|
||||||
## Phone Number Formatting
|
|
||||||
|
|
||||||
### Correct Formats
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Remove all non-digits
|
|
||||||
function formatPhone(phone) {
|
|
||||||
return phone.replace(/\D/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Examples
|
|
||||||
Input: +55 (11) 99999-9999
|
|
||||||
Output: 5511999999999
|
|
||||||
|
|
||||||
Input: +1-555-123-4567
|
|
||||||
Output: 15551234567
|
|
||||||
|
|
||||||
Input: 55 11 99999 9999
|
|
||||||
Output: 5511999999999
|
|
||||||
```
|
|
||||||
|
|
||||||
### Country Codes
|
|
||||||
|
|
||||||
| Country | Code | Example |
|
|
||||||
|---------|------|---------|
|
|
||||||
| Brazil | 55 | 5511999999999 |
|
|
||||||
| USA | 1 | 15551234567 |
|
|
||||||
| Mexico | 52 | 5215512345678 |
|
|
||||||
| Argentina | 54 | 5491112345678 |
|
|
||||||
| Portugal | 351 | 351912345678 |
|
|
||||||
|
|
||||||
## Webhook Payloads
|
|
||||||
|
|
||||||
### Incoming Message
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"object": "whatsapp_business_account",
|
|
||||||
"entry": [{
|
|
||||||
"id": "390727550789228",
|
|
||||||
"changes": [{
|
|
||||||
"value": {
|
|
||||||
"messaging_product": "whatsapp",
|
|
||||||
"metadata": {
|
|
||||||
"display_phone_number": "+553322980098",
|
|
||||||
"phone_number_id": "1158433381968079"
|
|
||||||
},
|
|
||||||
"contacts": [{
|
|
||||||
"profile": {
|
|
||||||
"name": "John Doe"
|
|
||||||
},
|
|
||||||
"wa_id": "5511999999999"
|
|
||||||
}],
|
|
||||||
"messages": [{
|
|
||||||
"from": "5511999999999",
|
|
||||||
"id": "wamid.HBgLNTE1OTk5OTk5OTk5FQIAERgSMzg1QTlCNkE2RTlFRTdFNDdF",
|
|
||||||
"timestamp": "1704067200",
|
|
||||||
"text": {
|
|
||||||
"body": "Hello bot!"
|
|
||||||
},
|
|
||||||
"type": "text"
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
"field": "messages"
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Message Status
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"object": "whatsapp_business_account",
|
|
||||||
"entry": [{
|
|
||||||
"id": "390727550789228",
|
|
||||||
"changes": [{
|
|
||||||
"value": {
|
|
||||||
"status": "sent",
|
|
||||||
"id": "wamid.HBgLNTE1OTk5OTk5OTk5FQIAERgSMzg1QTlCNkE2RTlFRTdFNDdF",
|
|
||||||
"timestamp": "1704067201"
|
|
||||||
},
|
|
||||||
"field": "message_template_status_update"
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## TwiML Examples
|
|
||||||
|
|
||||||
### Gather Verification Code
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Response>
|
|
||||||
<Gather action="https://your-domain.com/twilio/gather"
|
|
||||||
method="POST"
|
|
||||||
numDigits="6"
|
|
||||||
timeout="10">
|
|
||||||
<Say voice="alice" language="pt-BR">
|
|
||||||
Please enter your verification code followed by the pound sign.
|
|
||||||
</Say>
|
|
||||||
</Gather>
|
|
||||||
<Redirect>
|
|
||||||
https://twimlets.com/voicemail?Email=your-email@example.com
|
|
||||||
</Redirect>
|
|
||||||
</Response>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Simple Voicemail
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Response>
|
|
||||||
<Say>Please leave your message after the tone.</Say>
|
|
||||||
<Record action="https://your-domain.com/twilio/recording"
|
|
||||||
method="POST"
|
|
||||||
maxLength="30" />
|
|
||||||
<Say>Thank you for your message.</Say>
|
|
||||||
</Response>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Diagnostic Commands
|
|
||||||
|
|
||||||
### Test Connectivity
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Test webhook endpoint
|
|
||||||
curl -X POST https://your-domain.com/webhooks/whatsapp \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"test": true}'
|
|
||||||
|
|
||||||
# Test Meta API
|
|
||||||
curl -X GET "https://graph.facebook.com/v18.0/1158433381968079" \
|
|
||||||
-H "Authorization: Bearer EAAQdlso6aM8BOwl..."
|
|
||||||
|
|
||||||
# Test Twilio webhook
|
|
||||||
curl -X POST https://your-domain.com/twilio/voice \
|
|
||||||
-d "CallSid=CA123&From=+1234567890&To=+553322980098"
|
|
||||||
|
|
||||||
# Check SSL certificate
|
|
||||||
openssl s_client -connect your-domain.com:443
|
|
||||||
```
|
|
||||||
|
|
||||||
### Monitor Logs
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# General Bots logs
|
|
||||||
tail -f .gbot/logs/bot.log
|
|
||||||
|
|
||||||
# Webhook server logs (PM2)
|
|
||||||
pm2 logs whatsapp-webhook
|
|
||||||
|
|
||||||
# System logs
|
|
||||||
journalctl -u whatsapp-webhook -f
|
|
||||||
|
|
||||||
# Twilio debugger
|
|
||||||
# https://console.twilio.com/us1/develop/monitor/debugger
|
|
||||||
|
|
||||||
# Meta webhook status
|
|
||||||
# https://developers.facebook.com/apps/YOUR_APP_ID/webhooks/
|
|
||||||
```
|
|
||||||
|
|
||||||
## Rate Limits
|
|
||||||
|
|
||||||
### WhatsApp Business API
|
|
||||||
|
|
||||||
| Tier | Messages/Day | Messages/Second |
|
|
||||||
|------|--------------|-----------------|
|
|
||||||
| Tier 1 | 1,000 | 1 |
|
|
||||||
| Tier 2 | 10,000 | 5 |
|
|
||||||
| Tier 3 | 100,000 | 50 |
|
|
||||||
| Tier 4 | Unlimited | 1,000 |
|
|
||||||
|
|
||||||
### Rate Limiting Implementation
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const rateLimiter = {
|
|
||||||
requests: [],
|
|
||||||
maxRequests: 50,
|
|
||||||
timeWindow: 60000, // 1 minute
|
|
||||||
|
|
||||||
canMakeRequest() {
|
|
||||||
const now = Date.now();
|
|
||||||
this.requests = this.requests.filter(t => now - t < this.timeWindow);
|
|
||||||
return this.requests.length < this.maxRequests;
|
|
||||||
},
|
|
||||||
|
|
||||||
recordRequest() {
|
|
||||||
this.requests.push(Date.now());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Formatting Syntax
|
|
||||||
|
|
||||||
### Text Formatting
|
|
||||||
|
|
||||||
```basic
|
|
||||||
REM Bold text
|
|
||||||
*bold text*
|
|
||||||
|
|
||||||
REM Italics
|
|
||||||
_italics_
|
|
||||||
|
|
||||||
REM Strikethrough
|
|
||||||
~strikethrough~
|
|
||||||
|
|
||||||
REM Monospace
|
|
||||||
```monospace```
|
|
||||||
|
|
||||||
REM Combined
|
|
||||||
*_bold and italic_*
|
|
||||||
|
|
||||||
REM Line breaks
|
|
||||||
Line 1
|
|
||||||
Line 2
|
|
||||||
```
|
|
||||||
|
|
||||||
### Message Examples
|
|
||||||
|
|
||||||
```basic
|
|
||||||
REM Formatted menu
|
|
||||||
SEND WHATSAPP TO "+5511999999999" WITH "🤖 *Bot Menu*" + CHR$(10) + CHR$(10) + "1. 📊 Status" + CHR$(10) + "2. 🌐 Weather" + CHR$(10) + "3. 📧 Contact"
|
|
||||||
|
|
||||||
REM Address
|
|
||||||
SEND WHATSAPP TO "+5511999999999" WITH "📍 *Address:*" + CHR$(10) + "123 Main St" + CHR$(10) + "São Paulo, SP" + CHR$(10) + "Brazil"
|
|
||||||
|
|
||||||
REM Code snippet
|
|
||||||
SEND WHATSAPP TO "+5511999999999" WITH "```bash" + CHR$(10) + "npm install" + CHR$(10) + "```"
|
|
||||||
```
|
|
||||||
|
|
||||||
## URLs
|
|
||||||
|
|
||||||
### Meta Platforms
|
|
||||||
|
|
||||||
```
|
|
||||||
Meta for Developers:
|
|
||||||
https://developers.facebook.com/
|
|
||||||
|
|
||||||
Meta Business Suite:
|
|
||||||
https://business.facebook.com/
|
|
||||||
|
|
||||||
WhatsApp Business API:
|
|
||||||
https://developers.facebook.com/docs/whatsapp/
|
|
||||||
|
|
||||||
Webhook Configuration:
|
|
||||||
https://developers.facebook.com/apps/YOUR_APP_ID/webhooks/
|
|
||||||
|
|
||||||
Message Templates:
|
|
||||||
https://business.facebook.com/latest/wa/manage/message-templates/
|
|
||||||
```
|
|
||||||
|
|
||||||
### Twilio Platforms
|
|
||||||
|
|
||||||
```
|
|
||||||
Twilio Console:
|
|
||||||
https://console.twilio.com/
|
|
||||||
|
|
||||||
Twilio Debugger:
|
|
||||||
https://console.twilio.com/us1/develop/monitor/debugger
|
|
||||||
|
|
||||||
TwiML Bins:
|
|
||||||
https://console.twilio.com/us1/develop/twiml/bins
|
|
||||||
|
|
||||||
Phone Numbers:
|
|
||||||
https://console.twilio.com/us1/develop/phone-numbers/manage/active
|
|
||||||
```
|
|
||||||
|
|
||||||
### General Bots
|
|
||||||
|
|
||||||
```
|
|
||||||
Documentation:
|
|
||||||
https://botbook.general-bots.com/
|
|
||||||
|
|
||||||
Community Discord:
|
|
||||||
https://discord.gg/general-bots
|
|
||||||
|
|
||||||
GitHub Repository:
|
|
||||||
https://github.com/general-bots/general-bots
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quick Troubleshooting
|
|
||||||
|
|
||||||
### Issue: Verification Code Not Received
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check Twilio webhook is configured
|
|
||||||
twilio phone-numerals:info +553322980098
|
|
||||||
|
|
||||||
# Test webhook manually
|
|
||||||
curl -X POST https://your-domain.com/twilio/voice \
|
|
||||||
-d "CallSid=CA123&From=+1234567890"
|
|
||||||
|
|
||||||
# Verify voice capability is enabled
|
|
||||||
# In Twilio Console: Phone Numbers > Active Numbers > Your Number
|
|
||||||
# Check "Voice" is enabled
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: Messages Not Sending
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Verify access token
|
|
||||||
curl -X GET "https://graph.facebook.com/v18.0/me" \
|
|
||||||
-H "Authorization: Bearer YOUR_TOKEN"
|
|
||||||
|
|
||||||
# Check phone number ID
|
|
||||||
curl -X GET "https://graph.facebook.com/v18.0/1158433381968079" \
|
|
||||||
-H "Authorization: Bearer YOUR_TOKEN"
|
|
||||||
|
|
||||||
# Test message format
|
|
||||||
# Ensure phone number: 5511999999999 (no +, no spaces)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: Webhook Not Receiving Messages
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Verify webhook subscription
|
|
||||||
curl -X GET "https://graph.facebook.com/v18.0/YOUR_APP_ID/subscriptions" \
|
|
||||||
-H "Authorization: Bearer YOUR_TOKEN"
|
|
||||||
|
|
||||||
# Test webhook endpoint
|
|
||||||
curl -X POST https://your-domain.com/webhooks/whatsapp \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"object":"whatsapp_business_account","entry":[]}'
|
|
||||||
|
|
||||||
# Check webhook is subscribed to "messages" field
|
|
||||||
# In Meta Dashboard: WhatsApp > API Setup > Webhook > Manage
|
|
||||||
```
|
|
||||||
|
|
||||||
## Code Snippets
|
|
||||||
|
|
||||||
### Validate Phone Number
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function validatePhoneNumber(phone) {
|
|
||||||
// Remove non-digits
|
|
||||||
const cleaned = phone.replace(/\D/g, '');
|
|
||||||
|
|
||||||
// Check length (10-15 digits)
|
|
||||||
if (cleaned.length < 10 || cleaned.length > 15) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if all digits
|
|
||||||
if (!/^\d+$/.test(cleaned)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cleaned;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Format WhatsApp Message
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function formatMessage(template, variables) {
|
|
||||||
let message = template;
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(variables)) {
|
|
||||||
message = message.replace(new RegExp(`{{${key}}}`, 'g'), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Usage
|
|
||||||
const template = 'Hello {{name}}, your order {{orderId}} is confirmed!';
|
|
||||||
const variables = { name: 'John', orderId: '12345' };
|
|
||||||
const message = formatMessage(template, variables);
|
|
||||||
// Result: "Hello John, your order 12345 is confirmed!"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Retry Logic
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
async function sendWithRetry(whatsapp, to, message, maxRetries = 3) {
|
|
||||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
||||||
try {
|
|
||||||
return await whatsapp.sendText(to, message);
|
|
||||||
} catch (error) {
|
|
||||||
if (attempt === maxRetries) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exponential backoff
|
|
||||||
const delay = Math.pow(2, attempt) * 1000;
|
|
||||||
await new Promise(resolve => setTimeout(resolve, delay));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment Checklist
|
|
||||||
|
|
||||||
### Development
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Node.js
|
|
||||||
npm install express twilio body-parser axios
|
|
||||||
|
|
||||||
# Python
|
|
||||||
pip install flask requests twilio
|
|
||||||
|
|
||||||
# BASIC
|
|
||||||
REM No installation required
|
|
||||||
```
|
|
||||||
|
|
||||||
### Production
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Reverse proxy (nginx)
|
|
||||||
apt install nginx
|
|
||||||
|
|
||||||
# Process manager (PM2)
|
|
||||||
npm install -g pm2
|
|
||||||
|
|
||||||
# SSL certificate (Let's Encrypt)
|
|
||||||
apt install certbot python3-certbot-nginx
|
|
||||||
```
|
|
||||||
|
|
||||||
## Meta Console URLs
|
|
||||||
|
|
||||||
### Direct Access
|
|
||||||
|
|
||||||
Replace with your IDs:
|
|
||||||
|
|
||||||
```
|
|
||||||
WhatsApp Settings:
|
|
||||||
https://business.facebook.com/latest/settings/whatsapp_account/?business_id=312254061496740&selected_asset_id=303621682831134&selected_asset_type=whatsapp-business-account
|
|
||||||
|
|
||||||
Webhook Configuration:
|
|
||||||
https://developers.facebook.com/apps/323250907549153/webhooks/
|
|
||||||
|
|
||||||
Message Templates:
|
|
||||||
https://business.facebook.com/latest/wa/manage/message-templates/?waba_id=390727550789228
|
|
||||||
|
|
||||||
API Usage:
|
|
||||||
https://developers.facebook.com/apps/323250907549153/usage/
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**For detailed documentation:** See [README.md](./README.md)
|
|
||||||
**For troubleshooting:** See [troubleshooting.md](./troubleshooting.md)
|
|
||||||
**For code examples:** See [examples.md](./examples.md)
|
|
||||||
**For webhook setup:** See [webhooks.md](./webhooks.md)
|
|
||||||
|
|
@ -1,298 +0,0 @@
|
||||||
# Quick Start Guide
|
|
||||||
|
|
||||||
Get your WhatsApp Business bot up and running in 30 minutes with this streamlined setup guide.
|
|
||||||
|
|
||||||
## Prerequisites Checklist
|
|
||||||
|
|
||||||
- [ ] Twilio account with $10+ credit
|
|
||||||
- [ ] Meta for Developers account
|
|
||||||
- [ ] Meta Business Suite account
|
|
||||||
- [ ] Publicly accessible webhook URL (use ngrok for testing)
|
|
||||||
- [ ] Basic command line knowledge
|
|
||||||
|
|
||||||
## 30-Minute Setup
|
|
||||||
|
|
||||||
### Step 1: Buy Twilio Number (5 minutes)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Log into Twilio Console
|
|
||||||
# https://console.twilio.com/
|
|
||||||
|
|
||||||
# Navigate to: Phone Numbers > Buy a Number
|
|
||||||
# Select: Voice capability (required!)
|
|
||||||
# Purchase number
|
|
||||||
# Example: +553322980098
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tip:** Choose a number from your target country for easier verification.
|
|
||||||
|
|
||||||
### Step 2: Create Meta App (5 minutes)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Go to Meta for Developers
|
|
||||||
# https://developers.facebook.com/apps/
|
|
||||||
|
|
||||||
# Click: Create App > Business type
|
|
||||||
# App name: "My WhatsApp Bot"
|
|
||||||
# Add product: WhatsApp
|
|
||||||
# Create WhatsApp Business Account (WABA)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Save these values:**
|
|
||||||
```
|
|
||||||
WABA ID: 390727550789228
|
|
||||||
Application ID: 323250907549153
|
|
||||||
Phone Number ID: (after verification)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Configure Twilio Webhook (5 minutes)
|
|
||||||
|
|
||||||
**Option A: TwiML Bin (Fastest)**
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<!-- Create TwiML Bin in Twilio Console -->
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Response>
|
|
||||||
<Gather action="https://twimlets.com/voicemail?Email=your-email@example.com" method="POST">
|
|
||||||
<Say voice="alice">Please enter your verification code.</Say>
|
|
||||||
</Gather>
|
|
||||||
</Response>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option B: ngrok + Node.js (Recommended)**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install dependencies
|
|
||||||
npm install express twilio body-parser
|
|
||||||
|
|
||||||
# Create server.js
|
|
||||||
```
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const express = require('express');
|
|
||||||
const twilio = require('twilio');
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
app.use(require('body-parser').urlencoded({ extended: false }));
|
|
||||||
|
|
||||||
app.post('/twilio/voice', (req, res) => {
|
|
||||||
const twiml = new twilio.twiml.VoiceResponse();
|
|
||||||
twiml.redirect('https://twimlets.com/voicemail?Email=your-email@example.com');
|
|
||||||
res.type('text/xml');
|
|
||||||
res.send(twiml.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(3000);
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start ngrok
|
|
||||||
ngrok http 3000
|
|
||||||
|
|
||||||
# Update Twilio number webhook to:
|
|
||||||
# https://abc123.ngrok.io/twilio/voice
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Verify Phone Number (5 minutes)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# In Meta Business Suite:
|
|
||||||
# 1. WhatsApp Accounts > Add Phone Number
|
|
||||||
# 2. Enter: +553322980098
|
|
||||||
# 3. Select: "Phone Call" (NOT SMS!)
|
|
||||||
# 4. Click: Verify
|
|
||||||
|
|
||||||
# Meta will call your Twilio number
|
|
||||||
# Check your email for the verification code
|
|
||||||
# Enter code in Meta dashboard
|
|
||||||
```
|
|
||||||
|
|
||||||
**Critical:** Select "Phone Call" verification - Twilio numbers don't support SMS!
|
|
||||||
|
|
||||||
### Step 5: Get API Credentials (3 minutes)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# In Meta for Developers:
|
|
||||||
# 1. Your App > WhatsApp > API Setup
|
|
||||||
# 2. Click: "Temporary Access Token"
|
|
||||||
# 3. Copy token (starts with EAAQ...)
|
|
||||||
# 4. Note Phone Number ID from URL
|
|
||||||
```
|
|
||||||
|
|
||||||
**Required credentials:**
|
|
||||||
```csv
|
|
||||||
whatsapp-api-key,EAAQdlso6aM8BOwlhc3yM6bbJkGyibQPGJd87zFDHtfaFoJDJPohMl2c5nXs4yYuuHwoXJWx0rQKo0VXgTwThPYzqLEZArOZBhCWPBUpq7YlkEJXFAgB6ZAb3eoUzZAMgNZCZA1sg11rT2G8e1ZAgzpRVRffU4jmMChc7ybcyIwbtGOPKZAXKcNoMRfUwssoLhDWr
|
|
||||||
whatsapp-phone-number-id,1158433381968079
|
|
||||||
whatsapp-business-account-id,390727550789228
|
|
||||||
whatsapp-webhook-verify-token,4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY
|
|
||||||
whatsapp-application-id,323250907549153
|
|
||||||
whatsapp-enabled,true
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 6: Configure Webhook (5 minutes)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start your webhook server
|
|
||||||
node server.js
|
|
||||||
|
|
||||||
# In Meta Developers:
|
|
||||||
# 1. WhatsApp > API Setup > Webhook > Edit
|
|
||||||
# 2. Webhook URL: https://your-domain.com/webhooks/whatsapp
|
|
||||||
# 3. Verify Token: 4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY
|
|
||||||
# 4. Click: Verify and Save
|
|
||||||
# 5. Subscribe to: messages
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 7: Configure General Bots (2 minutes)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Edit .gbot/config.csv
|
|
||||||
```
|
|
||||||
|
|
||||||
```csv
|
|
||||||
key,value
|
|
||||||
whatsapp-enabled,true
|
|
||||||
whatsapp-api-key,EAAQdlso6aM8BOwlhc3yM6bbJkGyibQPGJd87zFDHtfaFoJDJPohMl2c5nXs4yYuuHwoXJWx0rQKo0VXgTwThPYzqLEZArOZBhCWPBUpq7YlkEJXFAgB6ZAb3eoUzZAMgNZCZA1sg11rT2G8e1ZAgzpRVRffU4jmMChc7ybcyIwbtGOPKZAXKcNoMRfUwssoLhDWr
|
|
||||||
whatsapp-phone-number-id,1158433381968079
|
|
||||||
whatsapp-business-account-id,390727550789228
|
|
||||||
whatsapp-webhook-verify-token,4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY
|
|
||||||
whatsapp-application-id,323250907549153
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 8: Test Your Bot (5 minutes)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Send test message via API
|
|
||||||
curl -X POST \
|
|
||||||
'https://graph.facebook.com/v18.0/1158433381968079/messages' \
|
|
||||||
-H 'Authorization: Bearer EAAQdlso6aM8BOwl...' \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d '{
|
|
||||||
"messaging_product": "whatsapp",
|
|
||||||
"to": "5511999999999",
|
|
||||||
"type": "text",
|
|
||||||
"text": {"body": "Hello from General Bots!"}
|
|
||||||
}'
|
|
||||||
|
|
||||||
# Or use BASIC
|
|
||||||
```
|
|
||||||
|
|
||||||
```basic
|
|
||||||
REM Test your WhatsApp integration
|
|
||||||
SEND WHATSAPP TO "+5511999999999" WITH "Hello from General Bots!"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Your First WhatsApp Bot
|
|
||||||
|
|
||||||
Create a simple echo bot:
|
|
||||||
|
|
||||||
```basic
|
|
||||||
REM Simple WhatsApp Echo Bot
|
|
||||||
ON WHATSAPP MESSAGE RECEIVED
|
|
||||||
LET SENDER$ = GET WHATSAPP SENDER NUMBER
|
|
||||||
LET MESSAGE$ = GET WHATSAPP MESSAGE BODY
|
|
||||||
|
|
||||||
LOG "Message from " + SENDER$ + ": " + MESSAGE$
|
|
||||||
|
|
||||||
REM Echo back with acknowledgment
|
|
||||||
SEND WHATSAPP TO SENDER$ WITH "You said: " + MESSAGE$
|
|
||||||
END ON
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common First-Time Mistakes
|
|
||||||
|
|
||||||
❌ **Don't select SMS verification** - Use "Phone Call"
|
|
||||||
❌ **Don't hardcode tokens** - Use config.csv
|
|
||||||
❌ **Don't forget webhook subscriptions** - Subscribe to "messages"
|
|
||||||
❌ **Don't use + in phone numbers** - Format: 5511999999999
|
|
||||||
❌ **Don't ignore rate limits** - Max 1000 messages/second
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. **Create message templates** for business-initiated conversations
|
|
||||||
2. **Set up persistent storage** for conversation history
|
|
||||||
3. **Implement retry logic** for failed messages
|
|
||||||
4. **Add monitoring** for webhook health
|
|
||||||
5. **Review security best practices**
|
|
||||||
|
|
||||||
## Need Help?
|
|
||||||
|
|
||||||
- 📖 [Full Documentation](./README.md)
|
|
||||||
- 🔧 [Troubleshooting Guide](./troubleshooting.md)
|
|
||||||
- 💻 [Code Examples](./examples.md)
|
|
||||||
- 🌐 [Webhook Configuration](./webhooks.md)
|
|
||||||
- 💬 [Community Discord](https://discord.gg/general-bots)
|
|
||||||
|
|
||||||
## Verification Checklist
|
|
||||||
|
|
||||||
- [ ] Twilio number purchased with Voice capability
|
|
||||||
- [ ] Meta app created with WhatsApp product
|
|
||||||
- [ ] Phone number verified via phone call
|
|
||||||
- [ ] Access token generated and saved
|
|
||||||
- [ ] Webhook configured and verified
|
|
||||||
- [ ] Webhook subscribed to "messages"
|
|
||||||
- [ ] config.csv updated with all credentials
|
|
||||||
- [ ] Test message sent successfully
|
|
||||||
- [ ] Incoming webhook received
|
|
||||||
- [ ] Bot replied to test message
|
|
||||||
|
|
||||||
✅ **All checked? Your WhatsApp bot is live!**
|
|
||||||
|
|
||||||
## Quick Reference: Essential Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Test webhook connectivity
|
|
||||||
curl -X POST https://your-webhook.com/webhooks/whatsapp \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"test":true}'
|
|
||||||
|
|
||||||
# Check Meta API status
|
|
||||||
curl https://developers.facebook.com/status/
|
|
||||||
|
|
||||||
# View Twilio call logs
|
|
||||||
# https://console.twilio.com/us1/develop/monitor/logs/calls
|
|
||||||
|
|
||||||
# Test access token
|
|
||||||
curl -X GET "https://graph.facebook.com/v18.0/me" \
|
|
||||||
-H "Authorization: Bearer YOUR_TOKEN"
|
|
||||||
|
|
||||||
# Monitor bot logs
|
|
||||||
tail -f .gbot/logs/bot.log
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration Template
|
|
||||||
|
|
||||||
Copy this template and replace with your values:
|
|
||||||
|
|
||||||
```csv
|
|
||||||
# WhatsApp Business Configuration
|
|
||||||
whatsapp-enabled,true
|
|
||||||
whatsapp-api-key,YOUR_ACCESS_TOKEN_HERE
|
|
||||||
whatsapp-phone-number-id,YOUR_PHONE_NUMBER_ID_HERE
|
|
||||||
whatsapp-business-account-id,YOUR_WABA_ID_HERE
|
|
||||||
whatsapp-webhook-verify-token,YOUR_VERIFY_TOKEN_HERE
|
|
||||||
whatsapp-application-id,YOUR_APP_ID_HERE
|
|
||||||
whatsapp-from-number,+553322980098
|
|
||||||
|
|
||||||
# Optional: Advanced Settings
|
|
||||||
whatsapp-webhook-url,https://your-domain.com/webhooks/whatsapp
|
|
||||||
whatsapp-timeout,30000
|
|
||||||
whatsapp-retry-attempts,3
|
|
||||||
whatsapp-rate-limit,50
|
|
||||||
```
|
|
||||||
|
|
||||||
## Time-Saving Tips
|
|
||||||
|
|
||||||
💡 **Use ngrok for testing** - No need to deploy to test webhooks
|
|
||||||
💡 **Save all credentials immediately** - Tokens won't be shown again
|
|
||||||
💡 **Test with your own number first** - Verify everything works
|
|
||||||
💡 **Enable debug logging** - Troubleshoot issues faster
|
|
||||||
💡 **Set up monitoring early** - Catch problems before users do
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Estimated total time:** 30 minutes
|
|
||||||
**Difficulty:** Intermediate
|
|
||||||
**Cost:** ~$10/month (Twilio number + usage)
|
|
||||||
|
|
||||||
For detailed explanations, advanced configurations, and production deployment, see the [complete documentation](./README.md).
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,632 +0,0 @@
|
||||||
# Webhook Configuration Guide
|
|
||||||
|
|
||||||
This guide provides detailed instructions for configuring webhooks for both Twilio (voice call handling) and Meta (WhatsApp message handling) in your General Bots integration.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The integration requires two separate webhook configurations:
|
|
||||||
|
|
||||||
1. **Twilio Voice Webhook** - Handles incoming verification calls and captures verification codes
|
|
||||||
2. **Meta WhatsApp Webhook** - Receives incoming WhatsApp messages and status updates
|
|
||||||
|
|
||||||
## Twilio Webhook Configuration
|
|
||||||
|
|
||||||
### Purpose
|
|
||||||
|
|
||||||
The Twilio webhook is critical during the initial phone number verification phase. Since Twilio numbers don't support SMS verification, Meta must call your number and read a 6-digit code. Your webhook must:
|
|
||||||
|
|
||||||
1. Answer the incoming call from Meta
|
|
||||||
2. Capture the audio or DTMF tones (key presses)
|
|
||||||
3. Forward the verification code to your email or logging system
|
|
||||||
|
|
||||||
### Webhook URL Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
POST https://your-domain.com/twilio/voice
|
|
||||||
```
|
|
||||||
|
|
||||||
### Required HTTP Headers
|
|
||||||
|
|
||||||
Twilio sends these headers with every webhook request:
|
|
||||||
|
|
||||||
| Header | Description | Example |
|
|
||||||
|--------|-------------|---------|
|
|
||||||
| `X-Twilio-Signature` | Request signature for security | `RCYmLs...` |
|
|
||||||
| `Content-Type` | Always `application/x-www-form-urlencoded` | - |
|
|
||||||
|
|
||||||
### Request Body Parameters
|
|
||||||
|
|
||||||
When a call comes in, Twilio POSTs these parameters:
|
|
||||||
|
|
||||||
| Parameter | Description | Example |
|
|
||||||
|-----------|-------------|---------|
|
|
||||||
| `CallSid` | Unique call identifier | `CA1234567890ABCDEF1234567890ABCDEF` |
|
|
||||||
| `From` | Caller's phone number | `+1234567890` (Meta's verification number) |
|
|
||||||
| `To` | Your Twilio number | `+553322980098` |
|
|
||||||
| `CallStatus` | Current call status | `ringing` |
|
|
||||||
| `Direction` | Call direction | `inbound` |
|
|
||||||
|
|
||||||
### TwiML Response Format
|
|
||||||
|
|
||||||
Your webhook must respond with TwiML (Twilio Markup Language) XML:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Response>
|
|
||||||
<Gather action="https://your-domain.com/twilio/gather" method="POST" numDigits="6">
|
|
||||||
<Say voice="alice" language="pt-BR">
|
|
||||||
Please enter your verification code followed by the pound sign.
|
|
||||||
</Say>
|
|
||||||
</Gather>
|
|
||||||
<Redirect>https://twimlets.com/voicemail?Email=your-email@example.com</Redirect>
|
|
||||||
</Response>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Implementation Examples
|
|
||||||
|
|
||||||
#### Node.js/Express
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const express = require('express');
|
|
||||||
const twilio = require('twilio');
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
app.post('/twilio/voice', (req, res) => {
|
|
||||||
const twiml = new twilio.twiml.VoiceResponse();
|
|
||||||
|
|
||||||
const gather = twiml.gather({
|
|
||||||
action: '/twilio/gather',
|
|
||||||
method: 'POST',
|
|
||||||
numDigits: 6,
|
|
||||||
timeout: 10
|
|
||||||
});
|
|
||||||
|
|
||||||
gather.say({
|
|
||||||
voice: 'alice',
|
|
||||||
language: 'pt-BR'
|
|
||||||
}, 'Please enter your verification code followed by the pound key.');
|
|
||||||
|
|
||||||
// Fallback to voicemail if no input
|
|
||||||
twiml.redirect('https://twimlets.com/voicemail?Email=your-email@example.com');
|
|
||||||
|
|
||||||
res.type('text/xml');
|
|
||||||
res.send(twiml.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post('/twilio/gather', (req, res) => {
|
|
||||||
const verificationCode = req.body.Digits;
|
|
||||||
|
|
||||||
console.log('WhatsApp Verification Code:', verificationCode);
|
|
||||||
|
|
||||||
// Send email notification
|
|
||||||
sendEmail({
|
|
||||||
to: 'your-email@example.com',
|
|
||||||
subject: 'WhatsApp Verification Code',
|
|
||||||
body: `Your verification code is: ${verificationCode}`
|
|
||||||
});
|
|
||||||
|
|
||||||
const twiml = new twilio.twiml.VoiceResponse();
|
|
||||||
twiml.say('Thank you. Your code has been received.');
|
|
||||||
|
|
||||||
res.type('text/xml');
|
|
||||||
res.send(twiml.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(3000, () => {
|
|
||||||
console.log('Twilio webhook server running on port 3000');
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Python/Flask
|
|
||||||
|
|
||||||
```python
|
|
||||||
from flask import Flask, request, Response
|
|
||||||
from twilio.twiml.voice_response import VoiceResponse, Gather
|
|
||||||
import smtplib
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
@app.route('/twilio/voice', methods=['POST'])
|
|
||||||
def voice_webhook():
|
|
||||||
response = VoiceResponse()
|
|
||||||
|
|
||||||
gather = Gather(
|
|
||||||
action='/twilio/gather',
|
|
||||||
method='POST',
|
|
||||||
num_digits=6,
|
|
||||||
timeout=10
|
|
||||||
)
|
|
||||||
gather.say(
|
|
||||||
'Please enter your verification code followed by the pound key.',
|
|
||||||
voice='alice',
|
|
||||||
language='pt-BR'
|
|
||||||
)
|
|
||||||
response.append(gather)
|
|
||||||
|
|
||||||
# Fallback to voicemail
|
|
||||||
response.redirect('https://twimlets.com/voicemail?Email=your-email@example.com')
|
|
||||||
|
|
||||||
return Response(str(response), mimetype='text/xml')
|
|
||||||
|
|
||||||
@app.route('/twilio/gather', methods=['POST'])
|
|
||||||
def gather_webhook():
|
|
||||||
verification_code = request.form.get('Digits')
|
|
||||||
|
|
||||||
print(f'WhatsApp Verification Code: {verification_code}')
|
|
||||||
|
|
||||||
# Send email notification
|
|
||||||
send_email(
|
|
||||||
to='your-email@example.com',
|
|
||||||
subject='WhatsApp Verification Code',
|
|
||||||
body=f'Your verification code is: {verification_code}'
|
|
||||||
)
|
|
||||||
|
|
||||||
response = VoiceResponse()
|
|
||||||
response.say('Thank you. Your code has been received.')
|
|
||||||
|
|
||||||
return Response(str(response), mimetype='text/xml')
|
|
||||||
|
|
||||||
def send_email(to, subject, body):
|
|
||||||
# Implement email sending logic
|
|
||||||
pass
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
app.run(port=3000)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### BASIC (General Bots)
|
|
||||||
|
|
||||||
```basic
|
|
||||||
REM Twilio Voice Webhook Handler
|
|
||||||
ON WEBHOOK POST TO "/twilio/voice" DO
|
|
||||||
REM Create TwiML response
|
|
||||||
LET TWIML$ = "<?xml version=""1.0"" encoding=""UTF-8""?>"
|
|
||||||
TWIML$ = TWIML$ + "<Response>"
|
|
||||||
TWIML$ = TWIML$ + "<Gather action=""https://your-domain.com/twilio/gather"" method=""POST"" numDigits=""6"">"
|
|
||||||
TWIML$ = TWIML$ + "<Say voice=""alice"" language=""pt-BR"">"
|
|
||||||
TWIML$ = TWIML$ + "Please enter your verification code followed by the pound sign."
|
|
||||||
TWIML$ = TWIML$ + "</Say>"
|
|
||||||
TWIML$ = TWIML$ + "</Gather>"
|
|
||||||
TWIML$ = TWIML$ + "<Redirect>https://twimlets.com/voicemail?Email=your-email@example.com</Redirect>"
|
|
||||||
TWIML$ = TWIML$ + "</Response>"
|
|
||||||
|
|
||||||
REM Set response content type
|
|
||||||
SET RESPONSE HEADER "Content-Type" TO "text/xml"
|
|
||||||
PRINT TWIML$
|
|
||||||
END ON
|
|
||||||
|
|
||||||
REM Gather Handler (receives the DTMF input)
|
|
||||||
ON WEBHOOK POST TO "/twilio/gather" DO
|
|
||||||
REM Get the digits entered
|
|
||||||
LET CODE$ = GET FORM VALUE "Digits"
|
|
||||||
|
|
||||||
REM Log the verification code
|
|
||||||
LOG "WhatsApp Verification Code: " + CODE$
|
|
||||||
|
|
||||||
REM Send email notification
|
|
||||||
SEND MAIL TO "your-email@example.com" WITH SUBJECT "WhatsApp Verification Code" AND BODY "Your verification code is: " + CODE$
|
|
||||||
|
|
||||||
REM Create confirmation TwiML
|
|
||||||
LET TWIML$ = "<?xml version=""1.0"" encoding=""UTF-8""?>"
|
|
||||||
TWIML$ = TWIML$ + "<Response>"
|
|
||||||
TWIML$ = TWIML$ + "<Say>Thank you. Your code has been received.</Say>"
|
|
||||||
TWIML$ = TWIML$ + "</Response>"
|
|
||||||
|
|
||||||
SET RESPONSE HEADER "Content-Type" TO "text/xml"
|
|
||||||
PRINT TWIML$
|
|
||||||
END ON
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuring Twilio
|
|
||||||
|
|
||||||
1. **Navigate to your phone number**
|
|
||||||
- Go to Twilio Console > Phone Numbers > Active Numbers
|
|
||||||
- Click on your purchased number
|
|
||||||
|
|
||||||
2. **Configure Voice Webhook**
|
|
||||||
- Find "Voice & Fax" section
|
|
||||||
- Set "A Call Comes In" to your webhook URL
|
|
||||||
- Select HTTP POST method
|
|
||||||
- Example: `https://your-domain.com/twilio/voice`
|
|
||||||
|
|
||||||
3. **Save changes**
|
|
||||||
- Click "Save" to apply the configuration
|
|
||||||
|
|
||||||
### Webhook Security
|
|
||||||
|
|
||||||
Verify that requests come from Twilio:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const twilio = require('twilio');
|
|
||||||
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
|
|
||||||
|
|
||||||
app.post('/twilio/voice', (req, res) => {
|
|
||||||
const url = `https://${req.headers.host}${req.path}`;
|
|
||||||
const signature = req.headers['x-twilio-signature'];
|
|
||||||
|
|
||||||
if (client.validateRequest(url, req.body, signature)) {
|
|
||||||
// Request is from Twilio, process it
|
|
||||||
handleVoiceWebhook(req, res);
|
|
||||||
} else {
|
|
||||||
// Invalid signature
|
|
||||||
res.status(403).send('Invalid signature');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Meta WhatsApp Webhook Configuration
|
|
||||||
|
|
||||||
### Purpose
|
|
||||||
|
|
||||||
The Meta webhook receives:
|
|
||||||
- Incoming WhatsApp messages from users
|
|
||||||
- Message delivery status updates
|
|
||||||
- Message read receipts
|
|
||||||
- Webhook verification requests
|
|
||||||
|
|
||||||
### Webhook URL Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
POST https://your-domain.com/webhooks/whatsapp
|
|
||||||
```
|
|
||||||
|
|
||||||
### Required HTTP Headers
|
|
||||||
|
|
||||||
| Header | Description | Example |
|
|
||||||
|--------|-------------|---------|
|
|
||||||
| `X-Hub-Signature-256` | HMAC SHA-256 signature | `sha256=...` |
|
|
||||||
|
|
||||||
### Webhook Verification
|
|
||||||
|
|
||||||
When you first configure the webhook, Meta sends a GET request to verify your URL:
|
|
||||||
|
|
||||||
```
|
|
||||||
GET https://your-domain.com/webhooks/whatsapp?hub.verify_token=YOUR_TOKEN&hub.challenge=CHALLENGE_STRING
|
|
||||||
```
|
|
||||||
|
|
||||||
Your webhook must respond with the challenge:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
app.get('/webhooks/whatsapp', (req, res) => {
|
|
||||||
const mode = req.query['hub.mode'];
|
|
||||||
const token = req.query['hub.verify_token'];
|
|
||||||
const challenge = req.query['hub.challenge'];
|
|
||||||
|
|
||||||
const VERIFY_TOKEN = '4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY';
|
|
||||||
|
|
||||||
if (mode === 'subscribe' && token === VERIFY_TOKEN) {
|
|
||||||
console.log('Webhook verified');
|
|
||||||
res.status(200).send(challenge);
|
|
||||||
} else {
|
|
||||||
res.sendStatus(403);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Message Payload Structure
|
|
||||||
|
|
||||||
Meta sends JSON payloads with message data:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"object": "whatsapp_business_account",
|
|
||||||
"entry": [{
|
|
||||||
"id": "390727550789228",
|
|
||||||
"changes": [{
|
|
||||||
"value": {
|
|
||||||
"messaging_product": "whatsapp",
|
|
||||||
"metadata": {
|
|
||||||
"display_phone_number": "+553322980098",
|
|
||||||
"phone_number_id": "1158433381968079"
|
|
||||||
},
|
|
||||||
"contacts": [{
|
|
||||||
"profile": {
|
|
||||||
"name": "John Doe"
|
|
||||||
},
|
|
||||||
"wa_id": "5511999999999"
|
|
||||||
}],
|
|
||||||
"messages": [{
|
|
||||||
"from": "5511999999999",
|
|
||||||
"id": "wamid.HBgLNTE1OTk5OTk5OTk5FQIAERgSMzg1QTlCNkE2RTlFRTdFNDdF",
|
|
||||||
"timestamp": "1704067200",
|
|
||||||
"text": {
|
|
||||||
"body": "Hello, how can I help you?"
|
|
||||||
},
|
|
||||||
"type": "text"
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
"field": "messages"
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Implementation Examples
|
|
||||||
|
|
||||||
#### Node.js/Express
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
app.post('/webhooks/whatsapp', (req, res) => {
|
|
||||||
try {
|
|
||||||
const data = req.body;
|
|
||||||
|
|
||||||
// Check if this is a WhatsApp message
|
|
||||||
if (data.object === 'whatsapp_business_account') {
|
|
||||||
data.entry.forEach(entry => {
|
|
||||||
entry.changes.forEach(change => {
|
|
||||||
if (change.field === 'messages') {
|
|
||||||
const message = change.value.messages[0];
|
|
||||||
const from = message.from;
|
|
||||||
const body = message.text.body;
|
|
||||||
const messageId = message.id;
|
|
||||||
|
|
||||||
console.log(`Received message from ${from}: ${body}`);
|
|
||||||
|
|
||||||
// Process the message
|
|
||||||
processWhatsAppMessage(from, body, messageId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(200).send('OK');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Webhook error:', error);
|
|
||||||
res.status(500).send('Error');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function processWhatsAppMessage(from, body, messageId) {
|
|
||||||
// Implement your bot logic here
|
|
||||||
const response = await generateResponse(body);
|
|
||||||
|
|
||||||
// Send reply
|
|
||||||
await sendWhatsAppMessage(from, response);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Python/Flask
|
|
||||||
|
|
||||||
```python
|
|
||||||
@app.route('/webhooks/whatsapp', methods=['POST'])
|
|
||||||
def whatsapp_webhook():
|
|
||||||
try:
|
|
||||||
data = request.get_json()
|
|
||||||
|
|
||||||
if data.get('object') == 'whatsapp_business_account':
|
|
||||||
for entry in data.get('entry', []):
|
|
||||||
for change in entry.get('changes', []):
|
|
||||||
if change.get('field') == 'messages':
|
|
||||||
message = change['value']['messages'][0]
|
|
||||||
from_number = message['from']
|
|
||||||
body = message['text']['body']
|
|
||||||
message_id = message['id']
|
|
||||||
|
|
||||||
print(f"Received message from {from_number}: {body}")
|
|
||||||
|
|
||||||
# Process the message
|
|
||||||
process_whatsapp_message(from_number, body, message_id)
|
|
||||||
|
|
||||||
return 'OK', 200
|
|
||||||
except Exception as e:
|
|
||||||
print(f'Webhook error: {e}')
|
|
||||||
return 'Error', 500
|
|
||||||
|
|
||||||
def process_whatsapp_message(from_number, body, message_id):
|
|
||||||
# Implement your bot logic here
|
|
||||||
response = generate_response(body)
|
|
||||||
|
|
||||||
# Send reply
|
|
||||||
send_whatsapp_message(from_number, response)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### BASIC (General Bots)
|
|
||||||
|
|
||||||
```basic
|
|
||||||
REM Meta WhatsApp Webhook Handler
|
|
||||||
ON WEBHOOK POST TO "/webhooks/whatsapp" DO
|
|
||||||
REM Get the JSON payload
|
|
||||||
LET PAYLOAD$ = GET REQUEST BODY
|
|
||||||
|
|
||||||
REM Parse the JSON (requires JSON parser library)
|
|
||||||
LET OBJ = PARSE JSON PAYLOAD$
|
|
||||||
|
|
||||||
REM Check if this is a WhatsApp message
|
|
||||||
IF GET JSON PATH OBJ, "object" = "whatsapp_business_account" THEN
|
|
||||||
REM Get the message
|
|
||||||
LET MESSAGE = GET JSON PATH OBJ, "entry[0].changes[0].value.messages[0]"
|
|
||||||
|
|
||||||
REM Extract message details
|
|
||||||
LET FROM$ = GET JSON PATH MESSAGE, "from"
|
|
||||||
LET BODY$ = GET JSON PATH MESSAGE, "text.body"
|
|
||||||
LET ID$ = GET JSON PATH MESSAGE, "id"
|
|
||||||
|
|
||||||
REM Log the message
|
|
||||||
LOG "WhatsApp message from " + FROM$ + ": " + BODY$
|
|
||||||
|
|
||||||
REM Process the message asynchronously
|
|
||||||
SPAWN PROCESS WHATSAPP MESSAGE FROM$, BODY$, ID$
|
|
||||||
END IF
|
|
||||||
|
|
||||||
REM Respond with 200 OK
|
|
||||||
PRINT "OK"
|
|
||||||
SET RESPONSE STATUS TO 200
|
|
||||||
END ON
|
|
||||||
|
|
||||||
REM Message processor
|
|
||||||
SUB PROCESS WHATSAPP MESSAGE FROM$, BODY$, ID$
|
|
||||||
REM Generate a response
|
|
||||||
LET RESPONSE$ = GENERATE RESPONSE BODY$
|
|
||||||
|
|
||||||
REM Send the reply
|
|
||||||
SEND WHATSAPP TO FROM$ WITH RESPONSE$
|
|
||||||
END SUB
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuring Meta
|
|
||||||
|
|
||||||
1. **Navigate to WhatsApp API Setup**
|
|
||||||
- Go to Meta for Developers > Your App > WhatsApp > API Setup
|
|
||||||
|
|
||||||
2. **Edit Webhook**
|
|
||||||
- Click "Edit" next to Webhook
|
|
||||||
- Enter your webhook URL: `https://your-domain.com/webhooks/whatsapp`
|
|
||||||
- Enter your Verify Token: `4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY`
|
|
||||||
- Click "Verify and Save"
|
|
||||||
|
|
||||||
3. **Subscribe to Webhook Fields**
|
|
||||||
- Subscribe to: `messages`
|
|
||||||
- This ensures you receive all incoming messages
|
|
||||||
|
|
||||||
### Webhook Security
|
|
||||||
|
|
||||||
Implement signature verification:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const crypto = require('crypto');
|
|
||||||
|
|
||||||
app.post('/webhooks/whatsapp', (req, res) => {
|
|
||||||
const signature = req.headers['x-hub-signature-256'];
|
|
||||||
const payload = JSON.stringify(req.body);
|
|
||||||
const appSecret = 'YOUR_APP_SECRET'; // From Meta dashboard
|
|
||||||
|
|
||||||
const expectedSignature = 'sha256=' + crypto
|
|
||||||
.createHmac('sha256', appSecret)
|
|
||||||
.update(payload)
|
|
||||||
.digest('hex');
|
|
||||||
|
|
||||||
if (signature !== expectedSignature) {
|
|
||||||
console.error('Invalid webhook signature');
|
|
||||||
return res.status(403).send('Invalid signature');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process the webhook
|
|
||||||
processWebhook(req.body);
|
|
||||||
res.status(200).send('OK');
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing Webhooks
|
|
||||||
|
|
||||||
### Using Ngrok for Local Development
|
|
||||||
|
|
||||||
1. **Install ngrok**
|
|
||||||
```bash
|
|
||||||
npm install -g ngrok
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Start your local server**
|
|
||||||
```bash
|
|
||||||
node server.js
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Start ngrok**
|
|
||||||
```bash
|
|
||||||
ngrok http 3000
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Use the ngrok URL**
|
|
||||||
- Your webhook URL: `https://abc123.ngrok.io/webhooks/whatsapp`
|
|
||||||
|
|
||||||
### Testing Twilio Webhook
|
|
||||||
|
|
||||||
Use Twilio's webhook debugger:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST \
|
|
||||||
'https://your-domain.com/twilio/voice' \
|
|
||||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
|
||||||
-d 'CallSid=CA123&From=+1234567890&To=+553322980098&CallStatus=ringing&Direction=inbound'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing Meta Webhook
|
|
||||||
|
|
||||||
Use Meta's webhook testing tool:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST \
|
|
||||||
'https://your-domain.com/webhooks/whatsapp' \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-H 'X-Hub-Signature-256: sha256=...' \
|
|
||||||
-d '{
|
|
||||||
"object": "whatsapp_business_account",
|
|
||||||
"entry": [{
|
|
||||||
"id": "390727550789228",
|
|
||||||
"changes": [{
|
|
||||||
"value": {
|
|
||||||
"messaging_product": "whatsapp",
|
|
||||||
"messages": [{
|
|
||||||
"from": "5511999999999",
|
|
||||||
"text": {"body": "Test message"}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
"field": "messages"
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Production Considerations
|
|
||||||
|
|
||||||
### High Availability
|
|
||||||
|
|
||||||
- Deploy webhooks behind a load balancer
|
|
||||||
- Implement retry logic for failed deliveries
|
|
||||||
- Use a message queue (RabbitMQ, Redis) for async processing
|
|
||||||
- Monitor webhook health and set up alerts
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
|
|
||||||
- Respond to webhooks quickly (< 3 seconds)
|
|
||||||
- Process heavy operations asynchronously
|
|
||||||
- Use worker queues for message processing
|
|
||||||
- Implement rate limiting to prevent abuse
|
|
||||||
|
|
||||||
### Monitoring
|
|
||||||
|
|
||||||
- Log all webhook requests and responses
|
|
||||||
- Track delivery success rates
|
|
||||||
- Monitor response times
|
|
||||||
- Set up alerts for failures
|
|
||||||
- Use tools like Sentry, Datadog, or New Relic
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
|
|
||||||
**Problem: Webhook verification fails**
|
|
||||||
- Ensure verify token matches exactly
|
|
||||||
- Check that your endpoint returns the challenge
|
|
||||||
- Verify your URL is publicly accessible
|
|
||||||
|
|
||||||
**Problem: Messages not received**
|
|
||||||
- Check webhook logs for errors
|
|
||||||
- Verify subscription to `messages` field
|
|
||||||
- Ensure your server is online and responding
|
|
||||||
|
|
||||||
**Problem: Invalid signature errors**
|
|
||||||
- Verify your app secret is correct
|
|
||||||
- Check that you're computing the hash correctly
|
|
||||||
- Ensure you're using the raw request body
|
|
||||||
|
|
||||||
**Problem: Timeout errors**
|
|
||||||
- Optimize your webhook handler
|
|
||||||
- Move heavy processing to background jobs
|
|
||||||
- Increase server capacity if needed
|
|
||||||
|
|
||||||
### Debugging Tools
|
|
||||||
|
|
||||||
- **Twilio Debugger**: View all Twilio webhook attempts
|
|
||||||
- **Meta Webhook Debugging**: Enable in app settings
|
|
||||||
- **Ngrok Inspector**: Inspect requests in real-time
|
|
||||||
- **Webhook.site**: Test webhooks without a server
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
- Set up persistent storage for message history
|
|
||||||
- Implement message queue for reliability
|
|
||||||
- Add webhook retry logic
|
|
||||||
- Configure monitoring and alerting
|
|
||||||
- Set up automated testing
|
|
||||||
|
|
||||||
For more information on webhook security, see [Security Considerations](./README.md#security-considerations).
|
|
||||||
|
|
@ -433,11 +433,6 @@
|
||||||
- [LLM Providers](./18-appendix-external-services/llm-providers.md)
|
- [LLM Providers](./18-appendix-external-services/llm-providers.md)
|
||||||
- [Weather API](./18-appendix-external-services/weather.md)
|
- [Weather API](./18-appendix-external-services/weather.md)
|
||||||
- [Channel Integrations](./18-appendix-external-services/channels.md)
|
- [Channel Integrations](./18-appendix-external-services/channels.md)
|
||||||
- [Quick Start Guide](./18-appendix-external-services/whatsapp-quick-start.md)
|
|
||||||
- [Webhook Configuration](./18-appendix-external-services/whatsapp-webhooks.md)
|
|
||||||
- [Code Examples](./18-appendix-external-services/whatsapp-examples.md)
|
|
||||||
- [Troubleshooting](./18-appendix-external-services/whatsapp-troubleshooting.md)
|
|
||||||
- [Quick Reference](./18-appendix-external-services/whatsapp-quick-reference.md)
|
|
||||||
- [Storage Services](./18-appendix-external-services/storage.md)
|
- [Storage Services](./18-appendix-external-services/storage.md)
|
||||||
- [Directory Services](./18-appendix-external-services/directory.md)
|
- [Directory Services](./18-appendix-external-services/directory.md)
|
||||||
- [Attendance Queue](./18-appendix-external-services/attendance-queue.md)
|
- [Attendance Queue](./18-appendix-external-services/attendance-queue.md)
|
||||||
|
|
@ -457,7 +452,5 @@
|
||||||
- [LXC Migration](./19-maintenance/lxc-migration.md)
|
- [LXC Migration](./19-maintenance/lxc-migration.md)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Glossary](./glossary.md)
|
[Glossary](./glossary.md)
|
||||||
[Contact](./contact/README.md)
|
[Contact](./contact/README.md)
|
||||||
|
|
|
||||||
|
|
@ -1,178 +1,103 @@
|
||||||
<svg width="800" height="720" viewBox="0 0 800 720" xmlns="http://www.w3.org/2000/svg" style="max-width: 100%; height: auto;">
|
<svg width="800" height="600" viewBox="0 0 800 600" xmlns="http://www.w3.org/2000/svg" style="max-width: 100%; height: auto;">
|
||||||
<style>
|
|
||||||
/* Light Mode Colors */
|
|
||||||
.title-text { fill: #1E1B4B; }
|
|
||||||
.main-text { fill: #334155; }
|
|
||||||
.box-stroke { stroke: #4B5563; }
|
|
||||||
.arrow-stroke { stroke: #6B7280; }
|
|
||||||
.arrow-fill { fill: #4B5563; }
|
|
||||||
.arrow-double-fill { fill: #6B7280; }
|
|
||||||
|
|
||||||
/* Box-specific colors */
|
|
||||||
.user-input-stroke { stroke: #3B82F6; }
|
|
||||||
.user-input-text { fill: #1E40AF; }
|
|
||||||
|
|
||||||
.bot-response-stroke { stroke: #10B981; }
|
|
||||||
.bot-response-text { fill: #047857; }
|
|
||||||
|
|
||||||
.websocket-stroke { stroke: #F97316; }
|
|
||||||
.websocket-text { fill: #C2410C; }
|
|
||||||
.websocket-subtext { fill: #FDBA74; }
|
|
||||||
|
|
||||||
.session-manager-stroke { stroke: #8B5CF6; }
|
|
||||||
.session-manager-text { fill: #7C3AED; }
|
|
||||||
.session-manager-steps { fill: #6D28D9; }
|
|
||||||
|
|
||||||
.valkey-stroke { stroke: #DC2626; }
|
|
||||||
.valkey-text { fill: #B91C1C; }
|
|
||||||
.valkey-subtext { fill: #EF4444; }
|
|
||||||
|
|
||||||
.postgres-stroke { stroke: #2563EB; }
|
|
||||||
.postgres-text { fill: #1D4ED8; }
|
|
||||||
.postgres-subtext { fill: #3B82F6; }
|
|
||||||
|
|
||||||
.features-stroke { stroke: #4B5563; }
|
|
||||||
.features-text { fill: #1F2937; }
|
|
||||||
|
|
||||||
.circle-stroke { stroke: #64748B; }
|
|
||||||
|
|
||||||
/* Dark Mode Colors */
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.title-text { fill: #F1F5F9; }
|
|
||||||
.main-text { fill: #E2E8F0; }
|
|
||||||
.box-stroke { stroke: #9CA3AF; }
|
|
||||||
.arrow-stroke { stroke: #9CA3AF; }
|
|
||||||
.arrow-fill { fill: #9CA3AF; }
|
|
||||||
.arrow-double-fill { fill: #A1A1AA; }
|
|
||||||
|
|
||||||
.user-input-stroke { stroke: #60A5FA; }
|
|
||||||
.user-input-text { fill: #93C5FD; }
|
|
||||||
|
|
||||||
.bot-response-stroke { stroke: #34D399; }
|
|
||||||
.bot-response-text { fill: #6EE7B7; }
|
|
||||||
|
|
||||||
.websocket-stroke { stroke: #FB923C; }
|
|
||||||
.websocket-text { fill: #FDBA74; }
|
|
||||||
.websocket-subtext { fill: #FED7AA; }
|
|
||||||
|
|
||||||
.session-manager-stroke { stroke: #A78BFA; }
|
|
||||||
.session-manager-text { fill: #C4B5FD; }
|
|
||||||
.session-manager-steps { fill: #DDD6FE; }
|
|
||||||
|
|
||||||
.valkey-stroke { stroke: #F87171; }
|
|
||||||
.valkey-text { fill: #FCA5A5; }
|
|
||||||
.valkey-subtext { fill: #FECACA; }
|
|
||||||
|
|
||||||
.postgres-stroke { stroke: #60A5FA; }
|
|
||||||
.postgres-text { fill: #93C5FD; }
|
|
||||||
.postgres-subtext { fill: #BFDBFE; }
|
|
||||||
|
|
||||||
.features-stroke { stroke: #6B7280; }
|
|
||||||
.features-text { fill: #F9FAFB; }
|
|
||||||
|
|
||||||
.circle-stroke { stroke: #94A3B8; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<defs>
|
<defs>
|
||||||
<marker id="arrow" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto" markerUnits="strokeWidth">
|
<marker id="arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
|
||||||
<path d="M0,0 L12,6 L0,12 z" class="arrow-fill"/>
|
<path d="M0,0 L0,6 L9,3 z" fill="#4B5563"/>
|
||||||
</marker>
|
</marker>
|
||||||
<marker id="arrow-double" markerWidth="16" markerHeight="12" refX="8" refY="6" orient="auto" markerUnits="strokeWidth">
|
<marker id="arrow-double" markerWidth="10" markerHeight="10" refX="5" refY="3" orient="auto" markerUnits="strokeWidth">
|
||||||
<path d="M0,0 L8,6 L0,12 z" class="arrow-double-fill"/>
|
<path d="M0,0 L0,6 L5,3 z" fill="#999"/>
|
||||||
<path d="M16,0 L8,6 L16,12 z" class="arrow-double-fill"/>
|
<path d="M5,0 L5,6 L10,3 z" fill="#999"/>
|
||||||
</marker>
|
</marker>
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<text x="400" y="30" text-anchor="middle" font-family="Arial, sans-serif" font-size="22" font-weight="bold" class="title-text">Session Manager Architecture</text>
|
<text x="400" y="30" text-anchor="middle" font-family="Arial, sans-serif" font-size="22" font-weight="bold" fill="#1F2937">Session Manager Architecture</text>
|
||||||
|
|
||||||
<!-- User Input -->
|
<!-- User Input -->
|
||||||
<rect x="50" y="60" width="150" height="50" fill="none" class="user-input-stroke" stroke-width="2" rx="8"/>
|
<rect x="50" y="60" width="150" height="50" fill="none" stroke="#63B3ED" stroke-width="2" rx="8"/>
|
||||||
<text x="125" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="bold" class="user-input-text">User Input</text>
|
<text x="125" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="bold" fill="#1E40AF">User Input</text>
|
||||||
|
|
||||||
<!-- Bot Response -->
|
<!-- Bot Response -->
|
||||||
<rect x="600" y="60" width="150" height="50" fill="none" class="bot-response-stroke" stroke-width="2" rx="8"/>
|
<rect x="600" y="60" width="150" height="50" fill="none" stroke="#68D391" stroke-width="2" rx="8"/>
|
||||||
<text x="675" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="bold" class="bot-response-text">Bot Response</text>
|
<text x="675" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="bold" fill="#10B981">Bot Response</text>
|
||||||
|
|
||||||
<!-- Arrows down from User Input and up to Bot Response -->
|
<!-- Arrows down from User Input and up to Bot Response -->
|
||||||
<line x1="125" y1="120" x2="125" y2="180" class="arrow-stroke" stroke-width="1.5" marker-end="url(#arrow)"/>
|
<line x1="125" y1="110" x2="125" y2="150" stroke="#888" stroke-width="2" marker-end="url(#arrow)"/>
|
||||||
<line x1="675" y1="180" x2="675" y2="120" class="arrow-stroke" stroke-width="1.5" marker-end="url(#arrow)"/>
|
<line x1="675" y1="150" x2="675" y2="110" stroke="#888" stroke-width="2" marker-end="url(#arrow)"/>
|
||||||
|
|
||||||
<!-- WebSocket/HTTP boxes -->
|
<!-- WebSocket/HTTP boxes -->
|
||||||
<rect x="50" y="180" width="150" height="60" fill="none" class="websocket-stroke" stroke-width="2" rx="6"/>
|
<rect x="50" y="150" width="150" height="60" fill="none" stroke="#F6AD55" stroke-width="2" rx="6"/>
|
||||||
<text x="125" y="205" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="bold" class="websocket-text">WebSocket</text>
|
<text x="125" y="175" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="bold" fill="#EA580C">WebSocket</text>
|
||||||
<text x="125" y="225" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" class="websocket-subtext">/HTTP</text>
|
<text x="125" y="195" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" fill="#FED7AA">/HTTP</text>
|
||||||
|
|
||||||
<rect x="600" y="180" width="150" height="60" fill="none" class="websocket-stroke" stroke-width="2" rx="6"/>
|
<rect x="600" y="150" width="150" height="60" fill="none" stroke="#F6AD55" stroke-width="2" rx="6"/>
|
||||||
<text x="675" y="205" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="bold" class="websocket-text">WebSocket</text>
|
<text x="675" y="175" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="bold" fill="#EA580C">WebSocket</text>
|
||||||
<text x="675" y="225" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" class="websocket-subtext">/HTTP</text>
|
<text x="675" y="195" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" fill="#FED7AA">/HTTP</text>
|
||||||
|
|
||||||
<!-- Arrows to/from Session Manager -->
|
<!-- Arrows to/from Session Manager -->
|
||||||
<line x1="125" y1="250" x2="125" y2="310" class="arrow-stroke" stroke-width="1.5" marker-end="url(#arrow)"/>
|
<line x1="125" y1="210" x2="125" y2="250" stroke="#888" stroke-width="2" marker-end="url(#arrow)"/>
|
||||||
<line x1="675" y1="310" x2="675" y2="250" class="arrow-stroke" stroke-width="1.5" marker-end="url(#arrow)"/>
|
<line x1="675" y1="250" x2="675" y2="210" stroke="#888" stroke-width="2" marker-end="url(#arrow)"/>
|
||||||
|
|
||||||
<!-- Session Manager Box -->
|
<!-- Session Manager Box -->
|
||||||
<rect x="100" y="310" width="600" height="130" fill="none" class="session-manager-stroke" stroke-width="3" rx="10"/>
|
<rect x="100" y="250" width="600" height="130" fill="none" stroke="#B794F4" stroke-width="3" rx="10"/>
|
||||||
<text x="400" y="340" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="bold" class="session-manager-text">Session Manager</text>
|
<text x="400" y="280" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="bold" fill="#9333EA">Session Manager</text>
|
||||||
|
|
||||||
<!-- Session Manager Steps -->
|
<!-- Session Manager Steps -->
|
||||||
<g font-family="Arial, sans-serif" font-size="18" class="session-manager-steps">
|
<g font-family="Arial, sans-serif" font-size="18" fill="#7C3AED">
|
||||||
<text x="150" y="370">1. Validate Token</text>
|
<text x="150" y="310">1. Validate Token</text>
|
||||||
<text x="150" y="390">2. Load Session</text>
|
<text x="150" y="330">2. Load Session</text>
|
||||||
<text x="150" y="410">3. Update State</text>
|
<text x="150" y="350">3. Update State</text>
|
||||||
|
|
||||||
<text x="450" y="370">4. Execute BASIC</text>
|
<text x="450" y="310">4. Execute BASIC</text>
|
||||||
<text x="450" y="390">5. Generate Response</text>
|
<text x="450" y="330">5. Generate Response</text>
|
||||||
<text x="450" y="410">6. Save History</text>
|
<text x="450" y="350">6. Save History</text>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Arrows down to databases -->
|
<!-- Arrows down to databases -->
|
||||||
<line x1="250" y1="450" x2="250" y2="490" class="arrow-stroke" stroke-width="1.5" marker-end="url(#arrow)"/>
|
<line x1="250" y1="380" x2="250" y2="420" stroke="#888" stroke-width="2" marker-end="url(#arrow)"/>
|
||||||
<line x1="550" y1="450" x2="550" y2="490" class="arrow-stroke" stroke-width="1.5" marker-end="url(#arrow)"/>
|
<line x1="550" y1="380" x2="550" y2="420" stroke="#888" stroke-width="2" marker-end="url(#arrow)"/>
|
||||||
|
|
||||||
<!-- Database boxes -->
|
<!-- Database boxes -->
|
||||||
<rect x="150" y="490" width="200" height="80" fill="none" class="valkey-stroke" stroke-width="2" rx="8"/>
|
<rect x="150" y="420" width="200" height="80" fill="none" stroke="#E53E3E" stroke-width="2" rx="8"/>
|
||||||
<text x="250" y="520" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="bold" class="valkey-text">Valkey</text>
|
<text x="250" y="450" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="bold" fill="#EF4444">Valkey</text>
|
||||||
<text x="250" y="540" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" class="valkey-subtext">(Cache)</text>
|
<text x="250" y="470" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" fill="#DC2626">(Cache)</text>
|
||||||
|
|
||||||
<rect x="450" y="490" width="200" height="80" fill="none" class="postgres-stroke" stroke-width="2" rx="8"/>
|
<rect x="450" y="420" width="200" height="80" fill="none" stroke="#4A90E2" stroke-width="2" rx="8"/>
|
||||||
<text x="550" y="520" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="bold" class="postgres-text">PostgreSQL</text>
|
<text x="550" y="450" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="bold" fill="#2563EB">PostgreSQL</text>
|
||||||
<text x="550" y="540" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" class="postgres-subtext">(Persist)</text>
|
<text x="550" y="470" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" fill="#1E40AF">(Persist)</text>
|
||||||
|
|
||||||
<!-- Sync arrow between databases -->
|
<!-- Sync arrow between databases -->
|
||||||
<path d="M 350 530 L 450 530" class="arrow-stroke" stroke-width="1.5" marker-end="url(#arrow-double)" stroke-dasharray="5,5" opacity="0.7"/>
|
<path d="M 350 460 L 450 460" stroke="#888" stroke-width="2" marker-end="url(#arrow-double)" stroke-dasharray="5,5" opacity="0.7"/>
|
||||||
<text x="400" y="525" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" class="main-text">Sync Every</text>
|
<text x="400" y="455" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#374151">Sync Every</text>
|
||||||
<text x="400" y="545" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" class="main-text">Message</text>
|
<text x="400" y="475" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#374151">Message</text>
|
||||||
|
|
||||||
<!-- Process Flow Numbers -->
|
<!-- Process Flow Numbers -->
|
||||||
<g font-family="Arial, sans-serif" font-size="16" class="main-text">
|
<g font-family="Arial, sans-serif" font-size="16" fill="#374151">
|
||||||
<circle cx="95" cy="140" r="12" fill="none" class="circle-stroke" stroke-width="1"/>
|
<circle cx="125" cy="130" r="12" fill="none" stroke="#718096" stroke-width="1"/>
|
||||||
<text x="95" y="144" text-anchor="middle">1</text>
|
<text x="125" y="134" text-anchor="middle">1</text>
|
||||||
|
|
||||||
<circle cx="95" cy="270" r="12" fill="none" class="circle-stroke" stroke-width="1"/>
|
<circle cx="125" cy="230" r="12" fill="none" stroke="#718096" stroke-width="1"/>
|
||||||
<text x="95" y="274" text-anchor="middle">2</text>
|
<text x="125" y="234" text-anchor="middle">2</text>
|
||||||
|
|
||||||
<circle cx="400" cy="375" r="12" fill="none" class="circle-stroke" stroke-width="1"/>
|
<circle cx="400" cy="315" r="12" fill="none" stroke="#718096" stroke-width="1"/>
|
||||||
<text x="400" y="379" text-anchor="middle">3</text>
|
<text x="400" y="319" text-anchor="middle">3</text>
|
||||||
|
|
||||||
<circle cx="705" cy="270" r="12" fill="none" class="circle-stroke" stroke-width="1"/>
|
<circle cx="675" cy="230" r="12" fill="none" stroke="#718096" stroke-width="1"/>
|
||||||
<text x="705" y="274" text-anchor="middle">4</text>
|
<text x="675" y="234" text-anchor="middle">4</text>
|
||||||
|
|
||||||
<circle cx="705" cy="140" r="12" fill="none" class="circle-stroke" stroke-width="1"/>
|
<circle cx="675" cy="130" r="12" fill="none" stroke="#718096" stroke-width="1"/>
|
||||||
<text x="705" y="144" text-anchor="middle">5</text>
|
<text x="675" y="134" text-anchor="middle">5</text>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Features box -->
|
<!-- Features box -->
|
||||||
<g id="features" transform="translate(50, 600)">
|
<g id="features" transform="translate(50, 520)">
|
||||||
<rect x="0" y="0" width="700" height="100" fill="none" class="features-stroke" stroke-width="1" rx="5" stroke-dasharray="2,2" opacity="0.5"/>
|
<rect x="0" y="0" width="700" height="60" fill="none" stroke="#4A5568" stroke-width="1" rx="5" stroke-dasharray="2,2" opacity="0.5"/>
|
||||||
<text x="350" y="20" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="bold" class="features-text">Key Features</text>
|
<text x="350" y="20" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#1F2937">Key Features</text>
|
||||||
|
|
||||||
<g font-family="Arial, sans-serif" font-size="14" class="main-text">
|
<g font-family="Arial, sans-serif" font-size="16" fill="#374151">
|
||||||
<text x="50" y="45">• Real-time WebSocket support</text>
|
<text x="50" y="45">• Real-time WebSocket support</text>
|
||||||
<text x="400" y="45">• Automatic session persistence</text>
|
<text x="250" y="45">• Automatic session persistence</text>
|
||||||
<text x="50" y="75">• Redis-compatible caching</text>
|
<text x="450" y="45">• Redis-compatible caching</text>
|
||||||
<text x="400" y="75">• ACID compliance</text>
|
<text x="600" y="45">• ACID compliance</text>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 6.1 KiB |
Loading…
Add table
Reference in a new issue