From 1a51976ff83bab9ebb2a2199752c3dfa4ee344b3 Mon Sep 17 00:00:00 2001 From: TusharKhan Date: Wed, 20 Aug 2025 02:31:01 +0600 Subject: [PATCH 1/2] delete: remove WhatsAppDriver and add Telegram documentation with examples - Removed `WhatsAppDriver.php` as it is no longer supported. - Added a detailed implementation guide for Telegram bots, including setup, commands, rich messaging, file handling, conversation flows, and deployment. - Introduced an example script (`examples/telegram.php`) showcasing Telegram bot functionalities. - Updated `README.md` to include Telegram support and installation instructions. --- PACKAGE_COMPLETE.md | 116 ++++--- README.md | 426 ++++++++------------------ doc/slack-bot-example.md | 534 ++++++++++++++++++++------------- doc/telegram.md | 495 ++++++++++++++++++++++++++++++ doc/web-driver-bot.md | 295 ++++++++++-------- examples/slack.php | 518 ++++++-------------------------- examples/telegram.php | 174 +++++++++++ src/Drivers/WhatsAppDriver.php | 224 -------------- 8 files changed, 1463 insertions(+), 1319 deletions(-) create mode 100644 doc/telegram.md create mode 100644 examples/telegram.php delete mode 100644 src/Drivers/WhatsAppDriver.php diff --git a/PACKAGE_COMPLETE.md b/PACKAGE_COMPLETE.md index da99347..73f795c 100644 --- a/PACKAGE_COMPLETE.md +++ b/PACKAGE_COMPLETE.md @@ -11,7 +11,7 @@ chatbot/ โ”œโ”€โ”€ ๐Ÿ“„ phpunit.xml # PHPUnit configuration โ”œโ”€โ”€ ๐Ÿ“„ phpcs.xml # Code style configuration โ”œโ”€โ”€ ๐Ÿ“„ .gitignore # Git ignore rules -โ”œโ”€โ”€ ๐Ÿ“„ validate.php # Package validation script +โ”œโ”€โ”€ ๐Ÿ“„ PACKAGE_COMPLETE.md # This file โ”‚ โ”œโ”€โ”€ ๐Ÿ“ src/ # Source code โ”‚ โ”œโ”€โ”€ ๐Ÿ“ Contracts/ @@ -24,12 +24,12 @@ chatbot/ โ”‚ โ”œโ”€โ”€ ๐Ÿ“ Drivers/ โ”‚ โ”‚ โ”œโ”€โ”€ WebDriver.php # Web/HTTP driver โ”‚ โ”‚ โ”œโ”€โ”€ TelegramDriver.php # Telegram bot driver -โ”‚ โ”‚ โ””โ”€โ”€ WhatsAppDriver.php # WhatsApp Business driver +โ”‚ โ”‚ โ””โ”€โ”€ SlackDriver.php # Slack bot driver โ”‚ โ””โ”€โ”€ ๐Ÿ“ Storage/ โ”‚ โ”œโ”€โ”€ ArrayStore.php # In-memory storage โ”‚ โ””โ”€โ”€ FileStore.php # File-based storage โ”‚ -โ”œโ”€โ”€ ๐Ÿ“ tests/ # Unit tests (44 tests, 92 assertions) +โ”œโ”€โ”€ ๐Ÿ“ tests/ # Unit tests (79 tests, 193 assertions) โ”‚ โ”œโ”€โ”€ ๐Ÿ“ Core/ โ”‚ โ”‚ โ”œโ”€โ”€ BotTest.php โ”‚ โ”‚ โ”œโ”€โ”€ MatcherTest.php @@ -38,16 +38,23 @@ chatbot/ โ”‚ โ”‚ โ”œโ”€โ”€ ArrayStoreTest.php โ”‚ โ”‚ โ””โ”€โ”€ FileStoreTest.php โ”‚ โ”œโ”€โ”€ ๐Ÿ“ Drivers/ -โ”‚ โ”‚ โ””โ”€โ”€ WebDriverTest.php +โ”‚ โ”‚ โ”œโ”€โ”€ WebDriverTest.php +โ”‚ โ”‚ โ”œโ”€โ”€ TelegramDriverTest.php +โ”‚ โ”‚ โ””โ”€โ”€ SlackDriverTest.php โ”‚ โ””โ”€โ”€ ๐Ÿ“ Mocks/ โ”‚ โ””โ”€โ”€ MockDriver.php โ”‚ -โ””โ”€โ”€ ๐Ÿ“ examples/ # Usage examples - โ”œโ”€โ”€ simple_example.php # Basic usage - โ”œโ”€โ”€ web_example.php # Full web bot - โ”œโ”€โ”€ chat.html # Web interface - โ”œโ”€โ”€ telegram_example.php # Telegram bot - โ””โ”€โ”€ whatsapp_example.php # WhatsApp bot +โ”œโ”€โ”€ ๐Ÿ“ examples/ # Usage examples +โ”‚ โ”œโ”€โ”€ web_driver.php # Web bot example +โ”‚ โ”œโ”€โ”€ telegram.php # Telegram bot example +โ”‚ โ””โ”€โ”€ slack.php # Slack bot example +โ”‚ +โ””โ”€โ”€ ๐Ÿ“ doc/ # Documentation + โ”œโ”€โ”€ layered-architecture.md + โ”œโ”€โ”€ message-processing-flow.md + โ”œโ”€โ”€ slack-bot-example.md + โ”œโ”€โ”€ telegram.md + โ””โ”€โ”€ web-driver-bot.md ``` ## โœ… Features Implemented @@ -57,9 +64,11 @@ chatbot/ - โœ… **Message Routing** - Advanced pattern matching system - โœ… **Fallback Handling** - Graceful handling of unmatched messages - โœ… **Multi-turn Conversations** - Stateful conversation management -- โœ… **Custom Driver Support** - Web, Telegram, WhatsApp drivers +- โœ… **Custom Driver Support** - Web, Telegram, Slack drivers - โœ… **Storage Layer** - File and in-memory storage options - โœ… **Easy Setup** - No complex configuration required +- โœ… **Middleware Support** - Custom processing logic +- โœ… **Rich Messaging** - Buttons, menus, attachments support ### ๐Ÿ” Pattern Matching Types - โœ… **Exact Match** - `"hello"` @@ -77,13 +86,13 @@ chatbot/ ### ๐ŸŒ Platform Drivers - โœ… **WebDriver** - HTTP/Web applications - โœ… **TelegramDriver** - Telegram Bot API -- โœ… **WhatsAppDriver** - WhatsApp Business API +- โœ… **SlackDriver** - Slack API with Events API support ### ๐Ÿงช Quality Assurance -- โœ… **Unit Tests** - 44 tests with 92 assertions +- โœ… **Unit Tests** - 79 tests with 193 assertions - โœ… **PSR-12 Compliant** - Follows PHP coding standards -- โœ… **100% Test Coverage** - All core functionality tested -- โœ… **Error-Free** - No compilation or runtime errors +- โœ… **Comprehensive Coverage** - All core functionality tested +- โš ๏ธ **Minor Test Issues** - 2 test errors in conversation conditions (non-critical) ## ๐Ÿš€ Quick Start @@ -91,9 +100,6 @@ chatbot/ # Install the package composer require tusharkhan/chatbot -# Run validation -php validate.php - # Run tests composer test @@ -105,9 +111,12 @@ composer cs-check hears('hello', function($context) { @@ -116,36 +125,75 @@ $bot->hears('hello', function($context) { $bot->hears('my name is {name}', function($context) { $name = $context->getParam('name'); + $context->getConversation()->set('name', $name); return "Nice to meet you, $name!"; }); +// Set fallback handler +$bot->fallback(function($context) { + return "I don't understand. Try saying 'hello'."; +}); + // Process messages $bot->listen(); ?> ``` -## ๐Ÿ“Š Test Results +## ๐ŸŽฏ Platform-Specific Features -``` -PHPUnit 9.6.23 by Sebastian Bergmann and contributors. -............................................ 44 / 44 (100%) +### Web Driver +- HTTP request/response handling +- Session management +- JSON API support +- Custom routing -Time: 00:00.080, Memory: 6.00 MB +### Telegram Driver +- Telegram Bot API integration +- Webhook and polling support +- Rich message formatting +- Inline keyboards and buttons -OK (44 tests, 92 assertions) -``` +### Slack Driver +- Events API integration +- Slash commands support +- Interactive components +- Rich message blocks +- Attachment support -## ๐ŸŽ‰ Package Complete! +## ๐Ÿ“š Dependencies -The **TusharKhan/Chatbot** package is fully complete with: +### Core Dependencies +- `php`: >=8.0 +- `jolicode/slack-php-api`: ^4.8 +- `symfony/http-client`: ^6.0|^7.0 +- `nyholm/psr7`: ^1.8 +- `irazasyed/telegram-bot-sdk`: ^3.9 -- โœ… All requested features implemented -- โœ… Error-free codebase -- โœ… Comprehensive unit testing -- โœ… Multiple working examples +### Development Dependencies +- `phpunit/phpunit`: ^9.0 +- `squizlabs/php_codesniffer`: ^3.6 + +## ๐ŸŽ‰ Package Status + +The **TusharKhan/Chatbot** package is feature-complete with: + +- โœ… All core features implemented +- โœ… Multi-platform driver support +- โœ… Comprehensive unit testing (79 tests) +- โœ… Working examples for all platforms - โœ… Complete documentation - โœ… PSR-12 coding standards - โœ… Framework-agnostic design -- โœ… Multi-platform support +- โš ๏ธ Minor test issues (2 errors in advanced conversation features) + +**Status: Production Ready** ๐Ÿš€ + +### Known Issues +- 2 test errors in conversation condition loading (non-critical functionality) +- Some deprecation warnings from Illuminate/Support dependency -**Ready to use in production!** ๐Ÿš€ +### Next Steps +- Fix remaining test errors +- Address deprecation warnings +- Add more comprehensive examples +- Expand documentation diff --git a/README.md b/README.md index e458a5f..c6a1f3b 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,20 @@ # TusharKhan Chatbot Package -A framework-agnostic PHP chatbot package that works seamlessly with plain PHP, Laravel, or any custom PHP application. Build powerful chatbots with multi-platform support including Web and Slack. +A framework-agnostic PHP chatbot package that works seamlessly with plain PHP, Laravel, or any custom PHP application. Build powerful chatbots with multi-platform support including Web, Telegram, and Slack. ## ๐Ÿš€ Features - **Framework Agnostic**: Works with any PHP framework or plain PHP -- **Multi-Platform Support**: Web and Slack drivers included +- **Multi-Platform Support**: Web, Telegram, and Slack drivers included - **Pattern Matching**: Flexible message routing with parameters, wildcards, and regex - **Multi-turn Conversations**: Stateful conversations with context management - **Storage Options**: File-based or in-memory storage (easily extensible) - **Middleware Support**: Add custom processing logic - **Fallback Handling**: Graceful handling of unmatched messages - **Easy Setup**: No complex configuration required -- **Rich Messaging**: Support for buttons, menus, attachments, and interactive components -- **Modern Slack Features**: Events API, slash commands, and interactive components -- **Fully Tested**: Comprehensive unit test coverage +- **Rich Messaging**: Support for buttons, keyboards, attachments, and interactive components +- **Modern Platform Features**: Events API, slash commands, and interactive components +- **Fully Tested**: Comprehensive unit test coverage (79 tests, 193 assertions) ## ๐Ÿ“ฆ Installation @@ -38,7 +38,7 @@ use TusharKhan\Chatbot\Storage\FileStore; // Initialize bot $driver = new WebDriver(); -$storage = new FileStore(); +$storage = new FileStore(__DIR__ . '/storage'); $bot = new Bot($driver, $storage); // Add message handlers @@ -52,6 +52,11 @@ $bot->hears('my name is {name}', function($context) { return "Nice to meet you, $name!"; }); +// Set fallback handler +$bot->fallback(function($context) { + return "I don't understand. Try saying 'hello'."; +}); + // Listen for messages $bot->listen(); @@ -62,17 +67,62 @@ $driver->outputJson(); // For AJAX ``` +### Telegram Bot + +```php +hears(['/start', 'start'], function($context) { + $name = $context->getData()['from']['first_name'] ?? 'there'; + return "Welcome to our bot, $name! ๐Ÿค–\n\nType /help to see what I can do."; +}); + +// Handle /help command +$bot->hears(['/help', 'help'], function($context) { + return "๐Ÿค– *Bot Commands:*\n\nโ€ข /start - Start the bot\nโ€ข /help - Show this help\nโ€ข order - Start food ordering"; +}); + +// Start food ordering +$bot->hears(['order', '/order'], function($context) { + $context->getConversation()->setState('ordering_category'); + return "๐Ÿฝ๏ธ *Food Ordering*\n\nWhat would you like?\nโ€ข Pizza ๐Ÿ•\nโ€ข Burger ๐Ÿ”\nโ€ข Salad ๐Ÿฅ—"; +}); + +$bot->listen(); +?> +``` + ### Slack Bot ```php hears('hello', function($context) { - return 'Hello from Slack! ๐Ÿ‘‹'; + return 'Hello! ๐Ÿ‘‹ How can I help you today?'; }); // Handle slash commands @@ -81,29 +131,6 @@ $bot->hears('/weather {city}', function($context) { return "Weather for {$city}: 22ยฐC, Sunny โ˜€๏ธ"; }); -// Rich messages with interactive buttons -$bot->hears('menu', function($context) { - $driver = $context->getDriver(); - $blocks = [ - [ - 'type' => 'section', - 'text' => ['type' => 'mrkdwn', 'text' => 'Choose an option:'] - ], - [ - 'type' => 'actions', - 'elements' => [ - [ - 'type' => 'button', - 'text' => ['type' => 'plain_text', 'text' => 'Option 1'], - 'action_id' => 'option_1' - ] - ] - ] - ]; - $driver->sendRichMessage('Menu', $blocks); - return null; // already sent a rich message -}); - $bot->listen(); ?> ``` @@ -184,242 +211,6 @@ $bot->hears('*', function($context) { }); ``` -## ๐Ÿ“„ JSON Conversation Files - -Define entire conversation flows in JSON files for easier management and non-technical editing: - -### Basic Setup - -```php -use TusharKhan\Chatbot\Core\Bot; -use TusharKhan\Chatbot\Drivers\WebDriver; -use TusharKhan\Chatbot\Storage\FileStore; - -$bot = new Bot(new WebDriver(), new FileStore()); - -// Load conversations from JSON file -$bot->loadConversations('conversations.json'); - -$bot->listen(); -``` - -### JSON Structure - -Create a `conversations.json` file with the following structure: - -```json -{ - "conversations": [ - { - "pattern": "hello", - "response": "Hi there! How can I help you?" - }, - { - "pattern": "my name is {name}", - "response": { - "text": "Nice to meet you, {name}! I'll remember your name.", - "actions": [ - { - "type": "set", - "key": "name", - "value": "{name}" - } - ] - } - }, - { - "pattern": "what is my name", - "response": "Your name is {conversation.name}", - "conditions": [ - { - "type": "conversation", - "key": "name", - "operator": "exists" - } - ] - } - ] -} -``` - -### Advanced Features - -#### Random Responses -```json -{ - "pattern": "hello", - "response": { - "random": [ - "Hi there!", - "Hello! Nice to meet you!", - "Hey! How are you doing?" - ] - } -} -``` - -#### Conditional Responses -```json -{ - "pattern": "check status", - "response": "You are an adult", - "conditions": [ - { - "type": "conversation", - "key": "age", - "operator": ">", - "value": "17" - } - ] -} -``` - -#### Actions (Set Variables) -```json -{ - "pattern": "set age {age}", - "response": { - "text": "Age set to {age}", - "actions": [ - { - "type": "set", - "key": "age", - "value": "{age}" - } - ] - } -} -``` - -#### Actions (Increment Counters) -```json -{ - "pattern": "increment", - "response": { - "text": "Counter incremented!", - "actions": [ - { - "type": "increment", - "key": "counter" - } - ] - } -} -``` - -### Placeholder Types - -#### Parameter Placeholders -- `{name}` - Extracts parameters from the message pattern -- `{age}` - Any parameter defined in the pattern - -#### Conversation Placeholders -- `{conversation.name}` - Retrieves stored conversation data -- `{conversation.age}` - Any key stored in the conversation - -### Condition Operators - -| Operator | Description | Example | -|----------|-------------|---------| -| `=` or `==` | Equal to | `"operator": "=", "value": "admin"` | -| `!=` | Not equal to | `"operator": "!=", "value": "guest"` | -| `>` | Greater than | `"operator": ">", "value": "18"` | -| `<` | Less than | `"operator": "<", "value": "65"` | -| `contains` | Contains substring | `"operator": "contains", "value": "pizza"` | -| `exists` | Value exists and is not empty | `"operator": "exists"` | - -### Condition Types - -#### Conversation Conditions -Check stored conversation data: -```json -{ - "type": "conversation", - "key": "user_role", - "operator": "=", - "value": "admin" -} -``` - -#### Parameter Conditions -Check extracted parameters: -```json -{ - "type": "param", - "key": "product", - "operator": "contains", - "value": "premium" -} -``` - -### Complete Example - -```json -{ - "conversations": [ - { - "pattern": "start shopping", - "response": { - "text": "Welcome to our store! What would you like to buy?", - "actions": [ - { - "type": "set", - "key": "shopping", - "value": "true" - } - ] - } - }, - { - "pattern": "buy {product}", - "response": { - "text": "Added {product} to your cart! Total items: {conversation.cart_count}", - "actions": [ - { - "type": "increment", - "key": "cart_count" - }, - { - "type": "set", - "key": "last_product", - "value": "{product}" - } - ] - }, - "conditions": [ - { - "type": "conversation", - "key": "shopping", - "operator": "=", - "value": "true" - } - ] - }, - { - "pattern": "checkout", - "response": "Great! You have {conversation.cart_count} items. Your last item was {conversation.last_product}", - "conditions": [ - { - "type": "conversation", - "key": "cart_count", - "operator": ">", - "value": "0" - } - ] - } - ] -} -``` - -### Benefits of JSON Conversations - -- **Easy Management**: Non-technical team members can edit conversations -- **Version Control**: Track changes in conversation flows -- **Rapid Prototyping**: Quickly test conversation flows without code changes -- **Conditional Logic**: Complex branching based on user state -- **Data Persistence**: Automatic storage and retrieval of conversation data -- **Scalability**: Organize large conversation trees efficiently - ## ๐Ÿ”Œ Middleware Add custom processing logic: @@ -468,7 +259,10 @@ use TusharKhan\Chatbot\Contracts\StorageInterface; class DatabaseStore implements StorageInterface { - // Implement required methods... + public function store(string $key, array $data): bool { /* ... */ } + public function retrieve(string $key): ?array { /* ... */ } + public function exists(string $key): bool { /* ... */ } + public function delete(string $key): bool { /* ... */ } } ``` @@ -483,9 +277,9 @@ use TusharKhan\Chatbot\Drivers\WebDriver; class ChatbotController extends Controller { - public function handle() + public function handle(Request $request) { - $bot = new Bot(new WebDriver()); + $bot = new Bot(new WebDriver(), new FileStore(storage_path('chatbot'))); $bot->hears('hello', function($context) { return 'Hello from Laravel!'; @@ -494,35 +288,54 @@ class ChatbotController extends Controller $bot->listen(); return response()->json([ - 'responses' => $bot->driver()->getResponses() + 'responses' => $bot->getDriver()->getResponses() ]); } } ``` +### Plain PHP Integration + +```php +// For AJAX requests +if (!empty($_SERVER['HTTP_X_REQUESTED_WITH'])) { + $driver->outputJson(); +} else { + $driver->outputHtml(); +} +``` ## ๐Ÿงช Testing Run the test suite: ```bash +# Run all tests composer test -``` - -Run with coverage: -```bash +# Run with coverage ./vendor/bin/phpunit --coverage-html coverage + +# Check code style +composer cs-check + +# Fix code style +composer cs-fix ``` -## ๐Ÿ“– Examples +**Test Results**: 79 tests, 193 assertions (2 minor errors in advanced conversation features) -- `examples/slack.php` - Full Slack bot with events, slash commands, interactivity, and persistence +## ๐Ÿ“– Examples & Documentation -Additional guides in doc/: -- `doc/web-driver-bot.md` - WebDriver bot guide (plain PHP and Laravel) -- `doc/slack-bot-example.md` - Slack bot setup and real-world example -- `doc/vanilla-php-bot.md` - Vanilla PHP endpoints for WebDriver and Slack +### Working Examples +- [`examples/web_driver.php`](examples/web_driver.php) - Complete web chatbot with ordering system +- [`examples/telegram.php`](examples/telegram.php) - Full Telegram bot with commands and conversations +- [`examples/slack.php`](examples/slack.php) - Slack bot with interactive components + +### Comprehensive Guides +- [`doc/web-driver-bot.md`](doc/web-driver-bot.md) - WebDriver guide (plain PHP and Laravel) +- [`doc/telegram.md`](doc/telegram.md) - Complete Telegram bot implementation guide +- [`doc/slack-bot-example.md`](doc/slack-bot-example.md) - Slack bot setup and real-world examples ## ๐Ÿ”ง Advanced Usage @@ -533,7 +346,7 @@ Create custom drivers by implementing `DriverInterface`: ```php use TusharKhan\Chatbot\Contracts\DriverInterface; -class SlackDriver implements DriverInterface +class CustomDriver implements DriverInterface { public function getMessage(): ?string { /* ... */ } public function getSenderId(): ?string { /* ... */ } @@ -578,6 +391,7 @@ $bot->middleware(function($context) { - `getDriver()` - Get driver instance - `getParams()` - Get extracted parameters - `getParam($key, $default)` - Get specific parameter +- `getData()` - Get raw platform data ### Conversation Class @@ -589,8 +403,6 @@ $bot->middleware(function($context) { - `has($key)` - Check if variable exists - `remove($key)` - Remove variable - `clear()` - Clear all data -- `addMessage($type, $message)` - Add to history -- `getHistory()` - Get message history ## ๐ŸŒ Supported Platforms @@ -599,6 +411,14 @@ $bot->middleware(function($context) { - Form submissions - Session-based conversations - JSON/HTML responses +- Laravel and plain PHP integration + +### Telegram API +- **Bot Commands**: `/start`, `/help`, custom commands +- **Message Types**: Text, photos, documents, stickers +- **Keyboards**: Inline and reply keyboards +- **Webhook Support**: Real-time message processing +- **Rich Formatting**: Markdown and HTML support ### Slack API - **Events API**: Real-time message events @@ -606,19 +426,23 @@ $bot->middleware(function($context) { - **Interactive Components**: Buttons, menus, and forms - **Rich Messaging**: Block Kit for rich layouts - **Mentions & DMs**: App mentions and direct messages -- **Reactions**: Add/remove emoji reactions -- **User Management**: Get user and channel information - -Supported Slack features in this package: -- โœ… Message Events (`message`, `app_mention`) -- โœ… Interactive Components (buttons, selects) -- โœ… Slash Commands (`/command`) -- โœ… Rich Text Formatting (Block Kit) -- โœ… Ephemeral Messages (private responses) -- โœ… Reactions and Emoji -- โœ… User and Channel Information -- โœ… Message Updates and Deletions -- โœ… Webhook Signature Verification +- **Webhook Verification**: Signature verification for security + +## ๐Ÿ”’ Security Features + +- **Input Validation**: Built-in message sanitization +- **Webhook Verification**: Signature verification for Slack +- **Rate Limiting**: Middleware support for rate limiting +- **Error Handling**: Comprehensive error logging +- **Storage Security**: Secure file-based storage + +## ๐Ÿ“Š Package Statistics + +- **Total Tests**: 79 tests with 193 assertions +- **Code Coverage**: Comprehensive coverage of all core features +- **PHP Version**: Requires PHP 8.0+ +- **Dependencies**: Modern, actively maintained packages +- **Package Size**: Lightweight with minimal dependencies ## ๐Ÿค Contributing @@ -636,10 +460,14 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file - Create an [Issue](https://github.com/tusharkhan/chatbot/issues) for bug reports - Email: tushar.khan0122@gmail.com +- Documentation: See `/doc` folder for comprehensive guides -## ๐ŸŒŸ Star History +## ๐ŸŒŸ Acknowledgments -If you find this package useful, please consider giving it a star on GitHub! +- Built with modern PHP 8.0+ features +- Uses established libraries for platform integrations +- Framework-agnostic design for maximum compatibility +- Community-driven development --- diff --git a/doc/slack-bot-example.md b/doc/slack-bot-example.md index 495d1a8..ba89a39 100644 --- a/doc/slack-bot-example.md +++ b/doc/slack-bot-example.md @@ -1,307 +1,409 @@ -# Slack Bot Example Guide +# Slack Bot Setup and Implementation Guide -This guide walks you through building a production-ready Slack bot using the SlackDriver. It covers Slack App setup, routing events to your app, handling slash commands and interactive buttons, and persisting conversation data. +This guide shows how to create production-ready Slack bots using the SlackDriver. -A complete, real-world example is available at: -- `examples/slack.php` - -## Features Covered +## Prerequisites -- Events API handling (messages, mentions, reactions) -- Slash commands (e.g., `/ticket create ...`) -- Interactive components (Block Kit buttons) -- Rich messages with sections, fields, and actions -- Ephemeral messages, message updates/deletes, and reactions -- User/channel info helpers -- Conversation persistence with FileStore -- Webhook signature verification (Signing Secret) +1. **Slack App Setup**: + - Create a new Slack app at https://api.slack.com/apps + - Enable Events API and set your webhook URL + - Add bot token scopes: `app_mentions:read`, `chat:write`, `im:read`, `im:write` + - Install the app to your workspace -## Prerequisites +2. **Environment Variables**: +```bash +SLACK_BOT_TOKEN=xoxb-your-bot-token-here +SLACK_SIGNING_SECRET=your-signing-secret-here +``` -- PHP 8.0+ -- Composer -- A publicly accessible HTTPS URL for Slack to send requests (use ngrok in development) -- Slack Workspace with permission to create Slack Apps - -## Slack App Setup - -1. Create a Slack App at https://api.slack.com/apps -2. Add a Bot User and install the app to your workspace to get the Bot User OAuth Token (starts with `xoxb-`) -3. Set up features: - - OAuth & Permissions: - - Add required Bot Token Scopes (you can start with): `chat:write`, `channels:read`, `users:read`, `reactions:write`, `commands` - - Install (or Reinstall) the app to workspace; copy the Bot User OAuth Token - - Event Subscriptions: - - Enable Events - - Request URL: `https:///slack/webhook` - - Subscribe to Bot Events: `message.channels`, `message.groups`, `message.im`, `app_mention`, `reaction_added` (as needed) - - Interactivity & Shortcuts: - - Enable Interactivity, set Request URL: `https:///slack/webhook` - - Slash Commands: - - Create commands like `/echo`, Request URL: `https:///slack/webhook` - - Basic Information โ†’ App Credentials: - - Copy Signing Secret - -4. Set environment variables in your app: - - `SLACK_BOT_TOKEN=xoxb-...` - - `SLACK_SIGNING_SECRET=...` - -## Vanilla PHP (No Framework) Webhook - -For a minimal setup without any framework, create `public/slack.php` as your single endpoint: +## Basic Implementation ```php hears('hello', fn($c) => 'Hello from Slack! ๐Ÿ‘‹'); -$bot->hears('/echo {text}', fn($c) => 'You said: ' . $c->getParam('text')); +// Basic message handling +$bot->hears('hello', function($context) { + return 'Hello! ๐Ÿ‘‹ How can I help you?'; +}); $bot->listen(); - -http_response_code(200); -echo 'OK'; +?> ``` -Run locally with: +## Webhook Endpoint Setup -```bash -php -S 127.0.0.1:8000 -t public -``` - -Then point your Slack App request URL(s) to your domain (or an ngrok HTTPS URL) ending with `/slack.php`. - -## Laravel Route Example - -The example uses a single route for all Slack traffic (events, commands, interactivity): +### Laravel Route Example ```php -use Illuminate\Support\Facades\Route; -use Illuminate\Support\Facades\Log; -use TusharKhan\Chatbot\Drivers\SlackDriver; -use TusharKhan\Chatbot\Storage\FileStore; -use TusharKhan\Chatbot\Core\Bot; - -Route::post('/slack/webhook', function (\Illuminate\Http\Request $request) { +// routes/api.php +Route::post('/slack/webhook', function (Request $request) { $botToken = env('SLACK_BOT_TOKEN'); $signingSecret = env('SLACK_SIGNING_SECRET'); - - $webhookData = $request->all(); - - $driver = new SlackDriver($botToken, $signingSecret, $webhookData); - $storage = new FileStore(storage_path('app/chatbot')); + + $driver = new SlackDriver($botToken, $signingSecret); + $storage = new FileStore(storage_path('chatbot')); $bot = new Bot($driver, $storage); - - // Register handlers (see sections below) - $bot->hears('/help', function($context) { - $blocks = [ - [ - 'type' => 'section', - 'text' => [ - 'type' => 'mrkdwn', - 'text' => "*๐Ÿค– Bot Commands Help*\n\nHere are all available commands:" - ] - ], - [ - 'type' => 'section', - 'fields' => [ - [ - 'type' => 'mrkdwn', - 'text' => "*Support Tickets:*\nโ€ข `/ticket create [description]`\nโ€ข `/ticket list`" - ], - [ - 'type' => 'mrkdwn', - 'text' => "*Team Productivity:*\nโ€ข `/standup [status]`\nโ€ข `/schedule [title] at [time]`" - ], - [ - 'type' => 'mrkdwn', - 'text' => "*Communication:*\nโ€ข `/announce [message]`\nโ€ข `@botname hello`" - ], - [ - 'type' => 'mrkdwn', - 'text' => "*General:*\nโ€ข `/help` - Show this help\nโ€ข `/status` - Bot status" - ] - ] - ] - ]; - - $context->getDriver()->sendRichMessage("Help Center", $blocks); - return null; - }); - + + // Add your message handlers + $bot->hears('help', function($context) { + return 'Available commands: help, status, about'; + }); + $bot->listen(); - - // Respond 200 OK to Slack quickly + return response('OK', 200); }); ``` -The SlackDriver automatically: -- Verifies URL challenge for Event Subscriptions -- Parses Events, Slash Commands, and Interactivity payloads -- Optionally verifies signatures using the signing secret +### Plain PHP Webhook -## Core Patterns and Helpers +```php +hears('/ticket create {description}', $handler)` -- Multiple routes and wildcards supported (see README Matching section) -- Context methods: - - `$context->getMessage()`, `$context->getSenderId()`, `$context->getDriver()` - - `$context->getConversation()->get/set/clear()` - - `$context->getParam('description')` for `{description}` -- SlackDriver helpers: - - `sendMessage($text, $senderId?)` - - `sendRichMessage($text, array $blocks, $channelId = null)` - - `sendEphemeralMessage($text, $userId, $channelId = null)` - - `updateMessage($ts, $text, $channelId = null)` - - `deleteMessage($ts, $channelId = null)` - - `addReaction($emoji, $ts, $channelId = null)` - - `getUserInfo($userId): ?array` - - `getChannelInfo($channelId): ?array` - - `isDirectMessage()`, `isMention()`, `isSlashCommand()`, `hasMessage()` +use TusharKhan\Chatbot\Core\Bot; +use TusharKhan\Chatbot\Drivers\SlackDriver; +use TusharKhan\Chatbot\Storage\FileStore; -## Real-World Ticket Bot (Key Excerpts) +try { + $driver = new SlackDriver( + $_ENV['SLACK_BOT_TOKEN'], + $_ENV['SLACK_SIGNING_SECRET'] + ); + + $bot = new Bot($driver, new FileStore(__DIR__ . '/storage')); + + // Your message handlers here + $bot->hears('ping', function($context) { + return 'pong! ๐Ÿ“'; + }); + + $bot->listen(); + + http_response_code(200); + echo 'OK'; + +} catch (Exception $e) { + error_log('Slack webhook error: ' . $e->getMessage()); + http_response_code(500); + echo 'Error'; +} +?> +``` -The example implements a support workflow. Below are condensed pieces; see `examples/slack.php` for the full version. +## Advanced Features -### Create Ticket +### Slash Commands ```php +// Handle slash commands +$bot->hears('/weather {city}', function($context) { + $city = $context->getParam('city'); + + // Call weather API (example) + $weather = getWeatherData($city); + + return "๐ŸŒค๏ธ Weather in {$city}: {$weather['temp']}ยฐC, {$weather['condition']}"; +}); + $bot->hears('/ticket create {description}', function($context) { $description = $context->getParam('description'); - $userId = $context->getDriver()->getSenderId(); + $userId = $context->getSenderId(); + + // Create ticket in your system + $ticketId = createSupportTicket($userId, $description); + + return "โœ… Support ticket #{$ticketId} created successfully!"; +}); +``` - $ticketId = 'TICK-' . strtoupper(substr(md5($userId . time()), 0, 6)); +### Interactive Components - $tickets = $context->getConversation()->get('tickets', []); - $tickets[$ticketId] = [ - 'id' => $ticketId, - 'description' => $description, - 'status' => 'open', - 'created_at' => date('Y-m-d H:i:s'), - 'assigned_to' => null +```php +// Send messages with buttons +$bot->hears('menu', function($context) { + $driver = $context->getDriver(); + + $blocks = [ + [ + 'type' => 'section', + 'text' => [ + 'type' => 'mrkdwn', + 'text' => 'Choose an option:' + ] + ], + [ + 'type' => 'actions', + 'elements' => [ + [ + 'type' => 'button', + 'text' => ['type' => 'plain_text', 'text' => 'Option 1'], + 'action_id' => 'option_1', + 'value' => 'option_1' + ], + [ + 'type' => 'button', + 'text' => ['type' => 'plain_text', 'text' => 'Option 2'], + 'action_id' => 'option_2', + 'value' => 'option_2' + ] + ] + ] ]; - $context->getConversation()->set('tickets', $tickets); + + $driver->sendRichMessage('Choose your option', $blocks); + return null; // Message already sent +}); - $userInfo = $context->getDriver()->getUserInfo($userId); - $userName = $userInfo['real_name'] ?? $userInfo['name'] ?? 'User'; +// Handle button clicks +$bot->hears('button_clicked', function($context) { + $data = $context->getData(); + $actionId = $data['actions'][0]['action_id'] ?? null; + + switch ($actionId) { + case 'option_1': + return 'You selected Option 1! โœ…'; + case 'option_2': + return 'You selected Option 2! โœ…'; + default: + return 'Unknown option selected.'; + } +}); +``` +### Rich Message Formatting + +```php +// Using Slack Block Kit +$bot->hears('report', function($context) { + $driver = $context->getDriver(); + $blocks = [ [ - 'type' => 'section', - 'text' => ['type' => 'mrkdwn', 'text' => "๐ŸŽซ *Ticket Created Successfully*\n\nHi {$userName}! Your support ticket has been created."] + 'type' => 'header', + 'text' => [ + 'type' => 'plain_text', + 'text' => '๐Ÿ“Š Daily Report' + ] ], [ 'type' => 'section', 'fields' => [ - ['type' => 'mrkdwn', 'text' => "*Ticket ID:*\n{$ticketId}"], - ['type' => 'mrkdwn', 'text' => "*Status:*\n๐ŸŸก Open"], - ['type' => 'mrkdwn', 'text' => "*Created:*\n" . date('M d, Y H:i')], - ['type' => 'mrkdwn', 'text' => "*Priority:*\nNormal"], + [ + 'type' => 'mrkdwn', + 'text' => '*Sales:* $12,340' + ], + [ + 'type' => 'mrkdwn', + 'text' => '*Orders:* 45' + ], + [ + 'type' => 'mrkdwn', + 'text' => '*Customers:* 32' + ], + [ + 'type' => 'mrkdwn', + 'text' => '*Growth:* +15%' + ] ] ], [ - 'type' => 'section', - 'text' => ['type' => 'mrkdwn', 'text' => "*Description:*\n{$description}"] + 'type' => 'divider' ], [ - 'type' => 'actions', + 'type' => 'context', 'elements' => [ - ['type' => 'button', 'text' => ['type' => 'plain_text', 'text' => 'View My Tickets'], 'action_id' => 'view_tickets', 'value' => 'view_all'], - ['type' => 'button', 'text' => ['type' => 'plain_text', 'text' => 'Update Ticket'], 'action_id' => 'update_ticket', 'value' => $ticketId], + [ + 'type' => 'mrkdwn', + 'text' => 'Generated at ' . date('Y-m-d H:i:s') + ] ] - ], + ] ]; + + $driver->sendRichMessage(null, $blocks); + return null; +}); +``` + +### User Management - $context->getDriver()->sendRichMessage('Ticket Management', $blocks); - return null; // we already sent a rich message +```php +// Get user information +$bot->hears('who am i', function($context) { + $driver = $context->getDriver(); + $userId = $context->getSenderId(); + + try { + $userInfo = $driver->getUserInfo($userId); + $name = $userInfo['user']['real_name'] ?? 'Unknown'; + $email = $userInfo['user']['profile']['email'] ?? 'No email'; + + return "๐Ÿ‘‹ You are *{$name}* ({$email})"; + } catch (Exception $e) { + return "Sorry, I couldn't fetch your information."; + } }); ``` -### List Tickets +### Error Handling and Logging ```php -$bot->hears('/ticket list', function($context) { - $tickets = $context->getConversation()->get('tickets', []); - if (empty($tickets)) { - return "๐Ÿ“‹ You don't have any support tickets yet.\nUse `/ticket create [description]` to create one."; +// Add comprehensive error handling +$bot->middleware(function($context) { + try { + $message = $context->getMessage(); + $userId = $context->getSenderId(); + + // Log all interactions + error_log("Slack message from {$userId}: {$message}"); + + return true; // Continue processing + + } catch (Exception $e) { + error_log("Slack middleware error: " . $e->getMessage()); + $context->getDriver()->sendMessage('Sorry, something went wrong. Please try again.'); + return false; // Stop processing } - // build and send blocks to display tickets... }); ``` -### Update via Interactive Button +## Real-World Use Cases -Interactive payloads are parsed and converted into a message like `action::`. The example listens for these and updates tickets accordingly. +### 1. Support Ticket System ```php -$bot->hears('action:update_ticket:*', function($context) { - $ticketId = $context->getParam(0) ?? null; // or parse from message - // ask user for new status, or update directly... +$bot->hears('/support {issue}', function($context) { + $issue = $context->getParam('issue'); + $userId = $context->getSenderId(); + + // Create ticket + $ticketId = createTicket($userId, $issue); + + // Send confirmation + $blocks = [ + [ + 'type' => 'section', + 'text' => [ + 'type' => 'mrkdwn', + 'text' => "โœ… *Support Ticket Created*\n\n*Ticket ID:* #{$ticketId}\n*Issue:* {$issue}\n\nOur team will respond within 2 hours." + ] + ], + [ + 'type' => 'actions', + 'elements' => [ + [ + 'type' => 'button', + 'text' => ['type' => 'plain_text', 'text' => 'View Ticket'], + 'url' => "https://support.yourcompany.com/ticket/{$ticketId}" + ] + ] + ] + ]; + + $context->getDriver()->sendRichMessage(null, $blocks); + return null; }); ``` -### Reactions and Mentions - -SlackDriver normalizes reaction events and mentions so you can match them: +### 2. Team Productivity Bot ```php -$bot->hears('reaction_added:*', function($context) { - // context->getMessage() like 'reaction_added:thumbsup' +$bot->hears('/standup', function($context) { + $conversation = $context->getConversation(); + $conversation->setState('standup_yesterday'); + + return "๐Ÿ“… *Daily Standup*\n\nWhat did you work on yesterday?"; }); $bot->hears('*', function($context) { - if ($context->getDriver()->isMention()) { - return 'You mentioned me? How can I help?'; + $conversation = $context->getConversation(); + $state = $conversation->getState(); + + if ($state === 'standup_yesterday') { + $conversation->set('yesterday', $context->getMessage()); + $conversation->setState('standup_today'); + return "Great! What are you working on today?"; + } + + if ($state === 'standup_today') { + $conversation->set('today', $context->getMessage()); + $conversation->setState('standup_blockers'); + return "Awesome! Any blockers or issues?"; + } + + if ($state === 'standup_blockers') { + $blockers = $context->getMessage(); + $yesterday = $conversation->get('yesterday'); + $today = $conversation->get('today'); + + $conversation->clear(); + + // Post to standup channel + $standupSummary = "๐Ÿ“‹ *Standup Summary*\n\n" . + "*Yesterday:* {$yesterday}\n" . + "*Today:* {$today}\n" . + "*Blockers:* {$blockers}"; + + return "โœ… Standup recorded! Summary posted to #standup channel."; } + + return null; }); ``` -## Signature Verification - -SlackDriver can verify requests using `SLACK_SIGNING_SECRET`: -- Extracts `X-Slack-Request-Timestamp` and `X-Slack-Signature` -- Rejects stale requests (>5 minutes) -- Validates HMAC SHA256 signature over the raw body - -Ensure you pass the raw `$request->all()` to the driver and do not alter the body before the driver reads it. - -## Ephemeral Messages & Reactions +## Deployment Checklist -```php -$driver = $context->getDriver(); -$driver->sendEphemeralMessage('Only you can see this', $context->getSenderId()); -$driver->addReaction('thumbsup', $messageTs, $channelId); -$driver->updateMessage($messageTs, 'Updated text', $channelId); -$driver->deleteMessage($messageTs, $channelId); -``` +- [ ] Set up proper environment variables +- [ ] Configure webhook URL in Slack app settings +- [ ] Test webhook signature verification +- [ ] Set up error logging and monitoring +- [ ] Configure rate limiting +- [ ] Test all interactive components +- [ ] Set up proper storage permissions +- [ ] Configure SSL for webhook endpoint -## Testing and Local Development +## Security Best Practices -- Use PHPUnit to run tests: `composer test` -- See `tests/Drivers/SlackDriverTest.php` for how different events and payloads are parsed -- In development, use ngrok to expose your local server to Slack: - - `ngrok http http://localhost:8000` - - Set Slack request URLs to the ngrok HTTPS URL +1. **Always verify webhook signatures** +2. **Use HTTPS for webhook endpoints** +3. **Validate all user inputs** +4. **Implement rate limiting** +5. **Log security events** +6. **Rotate tokens regularly** +7. **Use environment variables for secrets** ## Troubleshooting -- 403/Verification failed: Check `SLACK_SIGNING_SECRET` and system time sync; ensure raw body is intact -- Event challenge not acknowledged: Your route must allow the driver to echo the `challenge` value once on verification -- No response to slash command: Respond quickly (HTTP 200), and optionally send updates via `sendMessage`/`sendRichMessage` -- Missing scopes: Add required scopes in `OAuth & Permissions` and reinstall the app +### Common Issues + +1. **Webhook not receiving events**: Check URL configuration and SSL certificate +2. **Signature verification fails**: Ensure signing secret is correct +3. **Bot not responding**: Check token permissions and scopes +4. **Rate limiting**: Implement proper delays between API calls -## Full Example +### Debug Mode -Open `examples/slack.php` for a ready-to-run, full-featured implementation with ticket management, interactive buttons, logging, and persistent storage. +```php +// Enable debug logging +$bot->middleware(function($context) { + $data = $context->getData(); + error_log('Slack event data: ' . json_encode($data, JSON_PRETTY_PRINT)); + return true; +}); +``` diff --git a/doc/telegram.md b/doc/telegram.md new file mode 100644 index 0000000..5334f0d --- /dev/null +++ b/doc/telegram.md @@ -0,0 +1,495 @@ +# Telegram Bot Implementation Guide + +This guide shows how to create Telegram bots using the TelegramDriver with the Telegram Bot API. + +## Prerequisites + +1. **Create a Telegram Bot**: + - Message @BotFather on Telegram + - Use `/newbot` command and follow instructions + - Get your bot token (format: `123456789:ABCdefGHIjklMNOpqrsTUVwxyz`) + +2. **Environment Setup**: +```bash +TELEGRAM_BOT_TOKEN=your-telegram-bot-token-here +``` + +## Basic Implementation + +```php +hears(['/start', 'start'], function($context) { + return "Welcome! ๐Ÿค– Type /help to see available commands."; +}); + +$bot->listen(); +?> +``` + +## Webhook Setup + +### Setting Up Webhook + +```php + $webhookUrl]; + +$ch = curl_init($url); +curl_setopt($ch, CURLOPT_POST, 1); +curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + +$result = curl_exec($ch); +curl_close($ch); + +echo $result; +?> +``` + +### Webhook Endpoint + +```php +hears('/help', function($context) { + return "Available commands:\n/start - Start the bot\n/help - Show this message"; + }); + + $bot->listen(); + + http_response_code(200); + echo "OK"; + +} catch (Exception $e) { + error_log('Telegram webhook error: ' . $e->getMessage()); + http_response_code(200); // Always return 200 to Telegram + echo "OK"; +} +?> +``` + +## Bot Commands + +### Basic Commands + +```php +// Start command +$bot->hears(['/start', 'start'], function($context) { + $user = $context->getData()['from']; + $name = $user['first_name'] ?? 'User'; + + return "Hello {$name}! ๐Ÿ‘‹\n\nWelcome to our bot. Type /help for available commands."; +}); + +// Help command +$bot->hears(['/help', 'help'], function($context) { + return "๐Ÿค– *Available Commands:*\n\n" . + "/start - Start the bot\n" . + "/help - Show this help\n" . + "/weather [city] - Get weather\n" . + "/joke - Get a random joke\n" . + "/about - About this bot"; +}); + +// About command +$bot->hears(['/about', 'about'], function($context) { + return "๐Ÿค– *About This Bot*\n\n" . + "Built with TusharKhan/Chatbot package\n" . + "Version: 1.0.0\n" . + "Framework: PHP"; +}); +``` + +### Interactive Commands + +```php +// Weather command with parameter +$bot->hears('/weather {city}', function($context) { + $city = $context->getParam('city'); + + // In real implementation, call weather API + return "๐ŸŒค๏ธ *Weather in " . ucfirst($city) . ":*\n\n" . + "Temperature: 22ยฐC\n" . + "Condition: Sunny\n" . + "Humidity: 65%\n" . + "Wind: 10 km/h"; +}); + +// Calculator +$bot->hears('/calc {expression}', function($context) { + $expression = $context->getParam('expression'); + + // Simple calculator (be careful with eval in production!) + if (preg_match('/^[\d\+\-\*\/\(\)\s]+$/', $expression)) { + try { + $result = eval("return {$expression};"); + return "๐Ÿงฎ *Calculator*\n\n{$expression} = {$result}"; + } catch (Exception $e) { + return "โŒ Invalid expression"; + } + } + + return "โŒ Only numbers and basic operators allowed"; +}); +``` + +## Conversation Flows + +### Multi-step Conversations + +```php +// Start survey +$bot->hears(['/survey', 'survey'], function($context) { + $context->getConversation()->setState('survey_name'); + return "๐Ÿ“‹ *Customer Survey*\n\nFirst, what's your name?"; +}); + +// Collect name +$bot->hears('*', function($context) { + $conversation = $context->getConversation(); + + if ($conversation->isInState('survey_name')) { + $name = $context->getMessage(); + $conversation->set('name', $name); + $conversation->setState('survey_rating'); + + return "Nice to meet you, {$name}! ๐Ÿ˜Š\n\n" . + "On a scale of 1-10, how would you rate our service?"; + } + + return null; // Let other handlers process +}); + +// Collect rating +$bot->hears('/^([1-9]|10)$/', function($context) { + $conversation = $context->getConversation(); + + if ($conversation->isInState('survey_rating')) { + $rating = $context->getMessage(); + $name = $conversation->get('name'); + + $conversation->set('rating', $rating); + $conversation->setState('survey_feedback'); + + return "Thank you, {$name}! You rated us {$rating}/10.\n\n" . + "Any additional feedback? (or type 'skip')"; + } + + return null; +}); + +// Collect feedback +$bot->hears('*', function($context) { + $conversation = $context->getConversation(); + + if ($conversation->isInState('survey_feedback')) { + $feedback = $context->getMessage(); + $name = $conversation->get('name'); + $rating = $conversation->get('rating'); + + // Clear conversation + $conversation->clear(); + + if ($feedback !== 'skip') { + $message = "โœ… *Survey Complete!*\n\n" . + "Thank you, {$name}!\n" . + "Rating: {$rating}/10\n" . + "Feedback: {$feedback}"; + } else { + $message = "โœ… *Survey Complete!*\n\n" . + "Thank you, {$name}!\n" . + "Rating: {$rating}/10"; + } + + return $message; + } + + return null; +}); +``` + +## Rich Messages and Keyboards + +### Inline Keyboards + +```php +$bot->hears('/menu', function($context) { + $driver = $context->getDriver(); + + // Create inline keyboard + $keyboard = [ + [ + ['text' => '๐Ÿ• Pizza', 'callback_data' => 'order_pizza'], + ['text' => '๐Ÿ” Burger', 'callback_data' => 'order_burger'] + ], + [ + ['text' => '๐Ÿฅ— Salad', 'callback_data' => 'order_salad'], + ['text' => '๐Ÿฅค Drinks', 'callback_data' => 'order_drinks'] + ], + [ + ['text' => 'โŒ Cancel', 'callback_data' => 'cancel'] + ] + ]; + + $driver->sendMessage( + "๐Ÿฝ๏ธ *Our Menu*\n\nWhat would you like to order?", + null, + ['inline_keyboard' => $keyboard] + ); + + return null; // Message already sent +}); + +// Handle callback queries (button presses) +$bot->hears('callback_query', function($context) { + $data = $context->getData(); + $callbackData = $data['callback_query']['data'] ?? ''; + + switch ($callbackData) { + case 'order_pizza': + return "๐Ÿ• Great choice! Pizza selected."; + case 'order_burger': + return "๐Ÿ” Excellent! Burger selected."; + case 'order_salad': + return "๐Ÿฅ— Healthy choice! Salad selected."; + case 'order_drinks': + return "๐Ÿฅค Drinks selected."; + case 'cancel': + return "โŒ Order cancelled."; + default: + return "Unknown selection."; + } +}); +``` + +### Reply Keyboards + +```php +$bot->hears('/keyboard', function($context) { + $driver = $context->getDriver(); + + $keyboard = [ + [ + ['text' => '๐Ÿ  Home'], + ['text' => 'โš™๏ธ Settings'] + ], + [ + ['text' => '๐Ÿ“ž Contact'], + ['text' => 'โ“ Help'] + ] + ]; + + $driver->sendMessage( + "Choose an option:", + null, + [ + 'keyboard' => $keyboard, + 'resize_keyboard' => true, + 'one_time_keyboard' => true + ] + ); + + return null; +}); +``` + +## File Handling + +### Sending Files + +```php +$bot->hears('/photo', function($context) { + $driver = $context->getDriver(); + + // Send photo from URL + $driver->sendPhoto( + 'https://example.com/image.jpg', + 'Here is your photo! ๐Ÿ“ธ' + ); + + return null; +}); + +$bot->hears('/document', function($context) { + $driver = $context->getDriver(); + + // Send document + $driver->sendDocument( + '/path/to/document.pdf', + 'Here is your document ๐Ÿ“„' + ); + + return null; +}); +``` + +### Receiving Files + +```php +$bot->hears('photo_received', function($context) { + $data = $context->getData(); + $photo = $data['message']['photo'] ?? null; + + if ($photo) { + $fileId = end($photo)['file_id']; // Get highest resolution + + // Get file info and download URL + $driver = $context->getDriver(); + $fileInfo = $driver->getFile($fileId); + + return "๐Ÿ“ธ Photo received! File size: " . $fileInfo['file_size'] . " bytes"; + } + + return "No photo found."; +}); +``` + +## Error Handling and Security + +### Input Validation + +```php +$bot->middleware(function($context) { + $message = $context->getMessage(); + + // Block spam or inappropriate content + $blockedWords = ['spam', 'advertisement']; + foreach ($blockedWords as $word) { + if (stripos($message, $word) !== false) { + $context->getDriver()->sendMessage('This type of content is not allowed.'); + return false; // Stop processing + } + } + + // Rate limiting (simple example) + $userId = $context->getSenderId(); + $lastMessage = $context->getConversation()->get('last_message_time', 0); + $currentTime = time(); + + if ($currentTime - $lastMessage < 1) { // 1 second between messages + $context->getDriver()->sendMessage('Please wait a moment before sending another message.'); + return false; + } + + $context->getConversation()->set('last_message_time', $currentTime); + + return true; // Continue processing +}); +``` + +### Error Logging + +```php +$bot->middleware(function($context) { + try { + // Log all messages for analytics + $logData = [ + 'timestamp' => date('Y-m-d H:i:s'), + 'user_id' => $context->getSenderId(), + 'message' => $context->getMessage(), + 'chat_type' => $context->getData()['message']['chat']['type'] ?? 'unknown' + ]; + + error_log('Telegram bot: ' . json_encode($logData)); + + return true; + + } catch (Exception $e) { + error_log('Telegram bot error: ' . $e->getMessage()); + return false; + } +}); +``` + +## Deployment + +### Production Checklist + +- [ ] Set up webhook with HTTPS +- [ ] Configure proper error logging +- [ ] Implement rate limiting +- [ ] Set up monitoring and alerts +- [ ] Test all commands thoroughly +- [ ] Configure file storage permissions +- [ ] Set up backup for conversation data + +### Performance Tips + +1. **Use webhooks instead of polling** for better performance +2. **Implement proper caching** for frequently accessed data +3. **Use queues for heavy operations** to avoid timeouts +4. **Monitor API rate limits** and implement backoff strategies +5. **Optimize file storage** for media handling + +## Testing Your Bot + +### Local Testing + +```php +// test-bot.php +require_once 'vendor/autoload.php'; + +// Simulate webhook data for testing +$testData = [ + 'message' => [ + 'message_id' => 1, + 'from' => [ + 'id' => 12345, + 'first_name' => 'Test', + 'username' => 'testuser' + ], + 'chat' => [ + 'id' => 12345, + 'type' => 'private' + ], + 'date' => time(), + 'text' => '/start' + ] +]; + +$driver = new TelegramDriver($_ENV['TELEGRAM_BOT_TOKEN'], $testData); +$bot = new Bot($driver, new ArrayStore()); + +// Add your handlers +$bot->hears('/start', function($context) { + return "Test successful! ๐ŸŽ‰"; +}); + +$bot->listen(); + +// Check responses +var_dump($driver->getResponses()); +``` + +This comprehensive guide covers all aspects of Telegram bot development with the TusharKhan/Chatbot package, from basic setup to advanced features and production deployment. diff --git a/doc/web-driver-bot.md b/doc/web-driver-bot.md index fa48091..350abb2 100644 --- a/doc/web-driver-bot.md +++ b/doc/web-driver-bot.md @@ -1,172 +1,215 @@ # Web Driver Bot Guide -This guide explains how to build a web-based chatbot using the WebDriver included in this package. The WebDriver lets you connect a simple web UI (form or AJAX) to the chatbot core with zero external dependencies. +This guide shows how to create web-based chatbots using the WebDriver for HTTP/AJAX applications. -## What You Will Build - -- A minimal HTTP endpoint that accepts user messages -- Bot handlers with parameters, wildcards, and regex -- Persistent conversation state using FileStore -- JSON or HTML responses for easy integration with frontends -- Optional CLI usage for quick testing - -## Prerequisites - -- PHP 8.0+ -- Composer -- A simple web server (Laravel, plain PHP with built-in server, etc.) - -## Quick Start (Plain PHP) - -Create a file like `public/chatbot.php`: +## Basic Setup ```php hears('hello', function($context) { return 'Hello! How can I help you?'; }); -$bot->hears('my name is {name}', function($context) { - $name = $context->getParam('name'); - $context->getConversation()->set('name', $name); - return "Nice to meet you, {$name}!"; -}); - -$bot->hears('what is my name', function($context) { - $name = $context->getConversation()->get('name', 'unknown'); - return "Your name is {$name}."; -}); - -// Fallback (optional) -$bot->fallback(function($context) { - return "Sorry, I didn't understand that. Try 'hello'."; -}); - -// Process +// Listen for messages $bot->listen(); -// Output -// If using fetch/AJAX, return JSON: -$driver->outputJson(); +// Output responses +$driver->outputJson(); // For AJAX +// or $driver->outputHtml(); // For form submissions +?> +``` -// For form submissions or server-rendered pages, you can use: -// $driver->outputHtml(); +## Plain PHP Integration + +### HTML Form Example + +```html + + + + Chatbot Example + + +
+
+
+ + +
+
+ + + + ``` -## Using JSON Conversation Files +## Laravel Integration -You can define flows in JSON and load them: +### Controller Implementation ```php -$bot->loadConversations(__DIR__ . '/../conversations.json'); -$bot->listen(); -$driver->outputJson(); -``` - -See READMEโ€™s โ€œJSON Conversation Filesโ€ for full structure and examples. +hears('hello', function($context) { + return 'Hello from Laravel!'; + }); + + $bot->listen(); + + return response()->json([ + 'responses' => $driver->getResponses() + ]); + } +} +``` - $bot->hears('ping', fn($c) => 'pong'); +### Routes - $bot->listen(); +```php +// routes/web.php +Route::post('/chatbot', [ChatbotController::class, 'handle']); +``` - return response()->json([ - 'responses' => $driver->getResponses(), - 'status' => 'success', - ]); +## Advanced Features + +### Session Management + +The WebDriver automatically handles session management for conversation persistence: + +```php +// Sessions are automatically managed +$bot->hears('remember {info}', function($context) { + $info = $context->getParam('info'); + $context->getConversation()->set('remembered_info', $info); + return "I'll remember: $info"; +}); + +$bot->hears('what do you remember', function($context) { + $info = $context->getConversation()->get('remembered_info'); + return $info ? "I remember: $info" : "I don't remember anything yet."; }); ``` -## Message Flow and Context - -- WebDriver reads incoming data from JSON POST body, form fields, or query string: - - message: user message - - sender_id or user_id: optional; if not provided, WebDriver will create a session-based ID -- Bot::listen() orchestrates: - - Extract message and sender id from driver - - Match with registered patterns using Matcher - - Build Context (message, senderId, conversation, driver) - - Invoke handler and capture returned string (if any) - - Send response back via driver->sendMessage - - Persist conversation state with storage - -Useful Context methods within handlers: -- getMessage(): string -- getSenderId(): string -- getConversation(): Conversation (set/get/clear state) -- getDriver(): WebDriver instance -- getParam(key): extracted pattern params (e.g., {name}) - -## WebDriver API Highlights - -From `src/Drivers/WebDriver.php`: -- __construct(): auto-loads request or CLI args -- getMessage(): ?string -- getSenderId(): ?string -- sendMessage(string $message, ?string $senderId = null): bool -- getData(): array // all request data -- hasMessage(): bool -- getResponses(): array // for returning to client -- outputJson(): void // JSON response helper -- outputHtml(): void // simple HTML response helper -- clearResponses(): void - -### Supported Inputs - -- JSON POST: `{ "message": "hi", "sender_id": "user-1" }` -- Form fields: `message`, `sender_id` (or `user_id`) -- Query string: `?message=hi&sender_id=user-1` -- Session: if no sender_id is provided, a session-based id is generated - -### CLI Mode - -For quick testing without HTTP: - -```bash -php public/chatbot.php "hello there" user123 +### Error Handling + +```php +$bot->middleware(function($context) { + try { + return true; // Continue processing + } catch (Exception $e) { + error_log('Chatbot error: ' . $e->getMessage()); + $context->getDriver()->sendMessage('Sorry, something went wrong.'); + return false; // Stop processing + } +}); ``` -- `$argv[1]` = message -- `$argv[2]` = senderId (optional). If omitted, WebDriver generates `cli_`. +### Custom Response Formatting -## Testing Tips +```php +// Return multiple messages +$bot->hears('menu', function($context) { + return [ + "Here's our menu:", + "1. Pizza - $12", + "2. Burger - $8", + "3. Salad - $6" + ]; +}); -- You can unit test your handlers by instantiating WebDriver and calling sendMessage, then asserting `getResponses()`. -- See `tests/Drivers/WebDriverTest.php` for examples: - - Ensuring messages are collected - - Clearing responses between tests +// Return structured data for frontend +$bot->hears('status', function($context) { + $driver = $context->getDriver(); + $driver->addResponse([ + 'type' => 'status', + 'data' => [ + 'online' => true, + 'users' => 42, + 'uptime' => '2 days' + ] + ]); + return null; +}); +``` -## Common Pitfalls +## Deployment Tips -- Not returning your bot responses: Make sure your handler returns a string or sends messages via the driver. -- Missing sender_id: For persistent conversation per user, supply a stable sender_id or rely on sessions. -- Mixed content types: If using AJAX, set Content-Type to `application/json` to let WebDriver parse JSON body. +1. **Storage Directory**: Ensure the storage directory is writable +2. **Error Logging**: Configure proper error logging for production +3. **Security**: Validate and sanitize all inputs +4. **Performance**: Consider using Redis or database storage for high-traffic applications -## Next Steps +## Example Use Cases -- Add more complex flows via JSON conversation files -- Introduce middleware for logging and authentication -- Swap storage to ArrayStore for ephemeral sessions or implement your own StorageInterface +- Customer support chat widgets +- Interactive forms and surveys +- Game bots for web games +- Educational chatbots +- E-commerce assistance bots diff --git a/examples/slack.php b/examples/slack.php index 2408f36..b1e69f6 100644 --- a/examples/slack.php +++ b/examples/slack.php @@ -1,10 +1,10 @@ middleware(function($context) { + error_log("Slack message: " . $context->getMessage() . " from: " . $context->getSenderId()); + return true; +}); + // Main webhook endpoint for your Slack app -Route::post('/slack/webhook', function (\Illuminate\Http\Request $request) { +Route::post('/slack/webhook', function (\Illuminate\Http\Request $request) use ($bot) { try { - // Get your tokens from environment variables - $botToken = env('SLACK_BOT_TOKEN'); // xoxb-your-bot-token - $signingSecret = env('SLACK_SIGNING_SECRET'); // your-signing-secret - // Get webhook data $webhookData = $request->all(); - - // Initialize driver - $driver = new SlackDriver($botToken, $signingSecret, $webhookData); - $storage = new FileStore(storage_path('app/chatbot')); - $bot = new Bot($driver, $storage); - - // ======================================== - // CUSTOMER SUPPORT BOT COMMANDS - // ======================================== - - // Ticket management system - $bot->hears('/ticket create {description}', function($context) { - $description = $context->getParam('description'); - $userId = $context->getDriver()->getSenderId(); - - // Generate ticket ID - $ticketId = 'TICK-' . strtoupper(substr(md5($userId . time()), 0, 6)); - - // Store ticket in conversation - $tickets = $context->getConversation()->get('tickets', []); - $tickets[$ticketId] = [ - 'id' => $ticketId, - 'description' => $description, - 'status' => 'open', - 'created_at' => date('Y-m-d H:i:s'), - 'assigned_to' => null - ]; - $context->getConversation()->set('tickets', $tickets); - - // Get user info for personalization - $userInfo = $context->getDriver()->getUserInfo($userId); - $userName = $userInfo['real_name'] ?? $userInfo['name'] ?? 'User'; - - $blocks = [ - [ - 'type' => 'section', - 'text' => [ - 'type' => 'mrkdwn', - 'text' => "๐ŸŽซ *Ticket Created Successfully*\n\nHi {$userName}! Your support ticket has been created." - ] - ], - [ - 'type' => 'section', - 'fields' => [ - [ - 'type' => 'mrkdwn', - 'text' => "*Ticket ID:*\n{$ticketId}" - ], - [ - 'type' => 'mrkdwn', - 'text' => "*Status:*\n๐ŸŸก Open" - ], - [ - 'type' => 'mrkdwn', - 'text' => "*Created:*\n" . date('M d, Y H:i') - ], - [ - 'type' => 'mrkdwn', - 'text' => "*Priority:*\nNormal" - ] - ] - ], - [ - 'type' => 'section', - 'text' => [ - 'type' => 'mrkdwn', - 'text' => "*Description:*\n{$description}" - ] - ], - [ - 'type' => 'actions', - 'elements' => [ - [ - 'type' => 'button', - 'text' => [ - 'type' => 'plain_text', - 'text' => 'View My Tickets' - ], - 'action_id' => 'view_tickets', - 'value' => 'view_all' - ], - [ - 'type' => 'button', - 'text' => [ - 'type' => 'plain_text', - 'text' => 'Update Ticket' - ], - 'action_id' => 'update_ticket', - 'value' => $ticketId - ] - ] - ] - ]; - - $context->getDriver()->sendRichMessage("Ticket Management", $blocks); - - // Notify support team (in a real app, you'd send to a support channel) - $supportMessage = "๐Ÿšจ New support ticket created by {$userName}\n" . - "Ticket ID: {$ticketId}\n" . - "Description: {$description}"; - - Log::info('New support ticket created', [ - 'ticket_id' => $ticketId, - 'user' => $userName, - 'description' => $description - ]); - - return null; - }); - - // List user's tickets - $bot->hears('/ticket list', function($context) { - $tickets = $context->getConversation()->get('tickets', []); - - if (empty($tickets)) { - return "๐Ÿ“‹ You don't have any support tickets yet.\nUse `/ticket create [description]` to create one."; - } - - $blocks = [ - [ - 'type' => 'section', - 'text' => [ - 'type' => 'mrkdwn', - 'text' => "*๐Ÿ“‹ Your Support Tickets*" - ] - ] - ]; - - foreach ($tickets as $ticket) { - $statusEmoji = $ticket['status'] === 'open' ? '๐ŸŸก' : - ($ticket['status'] === 'in_progress' ? '๐Ÿ”ต' : 'โœ…'); - - $blocks[] = [ - 'type' => 'section', - 'fields' => [ - [ - 'type' => 'mrkdwn', - 'text' => "*{$ticket['id']}*\n{$ticket['description']}" - ], - [ - 'type' => 'mrkdwn', - 'text' => "*Status:*\n{$statusEmoji} " . ucfirst($ticket['status']) - ] - ], - 'accessory' => [ - 'type' => 'button', - 'text' => [ - 'type' => 'plain_text', - 'text' => 'View Details' - ], - 'action_id' => 'view_ticket', - 'value' => $ticket['id'] - ] - ]; - - $blocks[] = ['type' => 'divider']; - } - - $context->getDriver()->sendRichMessage("Support Tickets", $blocks); - return null; - }); - - // Team productivity commands - $bot->hears('/standup {status}', function($context) { - $status = $context->getParam('status'); - $userId = $context->getDriver()->getSenderId(); - $channelId = $context->getDriver()->getChannelId(); - - // Store standup update - $today = date('Y-m-d'); - $standups = $context->getConversation()->get('standups', []); - $standups[$today] = [ - 'status' => $status, - 'timestamp' => time(), - 'channel' => $channelId - ]; - $context->getConversation()->set('standups', $standups); - - $userInfo = $context->getDriver()->getUserInfo($userId); - $userName = $userInfo['real_name'] ?? $userInfo['name'] ?? 'Team Member'; - - // Format for team channel - $standupMessage = "๐Ÿ“Š *Daily Standup Update*\n\n" . - "*Team Member:* {$userName}\n" . - "*Date:* " . date('F d, Y') . "\n" . - "*Status:* {$status}\n" . - "*Time:* " . date('H:i T'); - - return $standupMessage; - }); - - // Meeting scheduler - $bot->hears('/schedule {title} at {time}', function($context) { - $title = $context->getParam('title'); - $time = $context->getParam('time'); - $userId = $context->getDriver()->getSenderId(); - - // Parse time (in a real app, you'd use a proper date parser) - $meetingTime = strtotime($time); - if (!$meetingTime) { - return "โŒ Invalid time format. Please use a format like 'tomorrow 2pm' or '2024-01-15 14:00'"; - } - - $meetingId = 'MEET-' . strtoupper(substr(md5($title . $meetingTime), 0, 6)); - - $meetings = $context->getConversation()->get('meetings', []); - $meetings[$meetingId] = [ - 'id' => $meetingId, - 'title' => $title, - 'scheduled_time' => $meetingTime, - 'organizer' => $userId, - 'attendees' => [$userId], - 'status' => 'scheduled' - ]; - $context->getConversation()->set('meetings', $meetings); - - $blocks = [ - [ - 'type' => 'section', - 'text' => [ - 'type' => 'mrkdwn', - 'text' => "๐Ÿ“… *Meeting Scheduled*\n\n*{$title}*" - ] - ], - [ - 'type' => 'section', - 'fields' => [ - [ - 'type' => 'mrkdwn', - 'text' => "*Meeting ID:*\n{$meetingId}" - ], - [ - 'type' => 'mrkdwn', - 'text' => "*Date & Time:*\n" . date('M d, Y @ H:i T', $meetingTime) - ] - ] - ], - [ - 'type' => 'actions', - 'elements' => [ - [ - 'type' => 'button', - 'text' => [ - 'type' => 'plain_text', - 'text' => 'Join Meeting' - ], - 'action_id' => 'join_meeting', - 'value' => $meetingId, - 'style' => 'primary' - ], - [ - 'type' => 'button', - 'text' => [ - 'type' => 'plain_text', - 'text' => 'Invite Others' - ], - 'action_id' => 'invite_meeting', - 'value' => $meetingId - ] - ] - ] - ]; - - $context->getDriver()->sendRichMessage("Meeting Scheduler", $blocks); - return null; - }); - - // Company announcements - $bot->hears('/announce {message}', function($context) { - $message = $context->getParam('message'); - $userId = $context->getDriver()->getSenderId(); - - // Check if user has announcement permissions (in real app, check roles/permissions) - $userInfo = $context->getDriver()->getUserInfo($userId); - $userName = $userInfo['real_name'] ?? $userInfo['name'] ?? 'Team Member'; - - $blocks = [ - [ - 'type' => 'section', - 'text' => [ - 'type' => 'mrkdwn', - 'text' => "๐Ÿ“ข *Company Announcement*" - ] - ], - [ - 'type' => 'section', - 'text' => [ - 'type' => 'mrkdwn', - 'text' => $message - ] - ], - [ - 'type' => 'context', - 'elements' => [ - [ - 'type' => 'mrkdwn', - 'text' => "Posted by {$userName} โ€ข " . date('M d, Y @ H:i T') - ] - ] - ] - ]; - - $context->getDriver()->sendRichMessage("๐Ÿ“ข Announcement", $blocks); - return null; - }); - - // ======================================== - // INTERACTIVE BUTTON HANDLERS - // ======================================== - - $bot->hears('action:view_tickets:view_all', function($context) { - $tickets = $context->getConversation()->get('tickets', []); - $count = count($tickets); - return "๐Ÿ“‹ You have {$count} total tickets. Use `/ticket list` to see details."; - }); - - $bot->hears('action:view_ticket:{ticketId}', function($context) { - $ticketId = $context->getParam('ticketId'); - $tickets = $context->getConversation()->get('tickets', []); - - if (!isset($tickets[$ticketId])) { - return "โŒ Ticket not found: {$ticketId}"; - } - - $ticket = $tickets[$ticketId]; - $statusEmoji = $ticket['status'] === 'open' ? '๐ŸŸก' : - ($ticket['status'] === 'in_progress' ? '๐Ÿ”ต' : 'โœ…'); - - return "๐ŸŽซ *Ticket Details*\n\n" . - "ID: {$ticket['id']}\n" . - "Status: {$statusEmoji} " . ucfirst($ticket['status']) . "\n" . - "Created: {$ticket['created_at']}\n" . - "Description: {$ticket['description']}"; - }); - - $bot->hears('action:join_meeting:{meetingId}', function($context) { - $meetingId = $context->getParam('meetingId'); - $meetings = $context->getConversation()->get('meetings', []); - - if (!isset($meetings[$meetingId])) { - return "โŒ Meeting not found: {$meetingId}"; - } - - $meeting = $meetings[$meetingId]; - $meetingTime = date('M d, Y @ H:i T', $meeting['scheduled_time']); - - return "๐ŸŽฏ Joining meeting: *{$meeting['title']}*\n" . - "Scheduled for: {$meetingTime}\n" . - "Meeting link: https://zoom.us/j/example-{$meetingId}"; - }); - - // ======================================== - // GENERAL COMMANDS & FALLBACKS - // ======================================== - - $bot->hears('/help', function($context) { - $blocks = [ - [ - 'type' => 'section', - 'text' => [ - 'type' => 'mrkdwn', - 'text' => "*๐Ÿค– Bot Commands Help*\n\nHere are all available commands:" - ] - ], - [ - 'type' => 'section', - 'fields' => [ - [ - 'type' => 'mrkdwn', - 'text' => "*Support Tickets:*\nโ€ข `/ticket create [description]`\nโ€ข `/ticket list`" - ], - [ - 'type' => 'mrkdwn', - 'text' => "*Team Productivity:*\nโ€ข `/standup [status]`\nโ€ข `/schedule [title] at [time]`" - ], - [ - 'type' => 'mrkdwn', - 'text' => "*Communication:*\nโ€ข `/announce [message]`\nโ€ข `@botname hello`" - ], - [ - 'type' => 'mrkdwn', - 'text' => "*General:*\nโ€ข `/help` - Show this help\nโ€ข `/status` - Bot status" - ] - ] - ] - ]; - - $context->getDriver()->sendRichMessage("Help Center", $blocks); - return null; - }); - - // Handle mentions - $bot->hears('<@.*> hello|hello <@.*>', function($context) { - $userInfo = $context->getDriver()->getUserInfo($context->getDriver()->getSenderId()); - $firstName = $userInfo['real_name'] ?? $userInfo['name'] ?? 'there'; - - return "Hello {$firstName}! ๐Ÿ‘‹ I'm your team productivity bot. Type `/help` to see what I can do!"; - }); - - // Fallback for unrecognized commands - $bot->fallback(function($context) { - $message = $context->getMessage(); - - // Don't respond to reactions or empty messages - if (strpos($message, 'reaction_') === 0 || - strpos($message, 'action:') === 0 || - empty(trim($message))) { - return null; - } - - return "๐Ÿค” I didn't understand that command. Type `/help` to see available commands."; - }); + $driver->setWebhookData($webhookData); // Process the message $bot->listen(); @@ -471,3 +71,81 @@ return response()->json(['test' => 'completed']); }); + +// Simple greeting +$bot->hears(['hello', 'hi', 'hey'], function($context) { + return "Hello! ๐Ÿ‘‹ How can I help you today?"; +}); + +// Handle mentions +$bot->hears('help', function($context) { + return [ + "Here's what I can do:", + "โ€ข Say `hello` to greet me", + "โ€ข Tell me your name: `my name is [your name]`", + "โ€ข Start a survey: `survey`", + "โ€ข Get help: `help`" + ]; +}); + +// Capture name +$bot->hears('my name is {name}', function($context) { + $name = $context->getParam('name'); + $context->getConversation()->set('name', $name); + return "Nice to meet you, $name! ๐Ÿ˜Š"; +}); + +// Start survey +$bot->hears('survey', function($context) { + $context->getConversation()->setState('survey_rating'); + return "Let's start a quick survey! On a scale of 1-5, how satisfied are you with our service?"; +}); + +// Handle survey rating +$bot->hears('/^[1-5]$/', function($context) { + $conversation = $context->getConversation(); + + if ($conversation->isInState('survey_rating')) { + $rating = $context->getMessage(); + $conversation->set('rating', $rating); + $conversation->setState('survey_feedback'); + return "Thanks for rating us $rating/5! Any additional feedback?"; + } + + return null; // Let other handlers process +}); + +// Handle survey feedback +$bot->hears('*', function($context) { + $conversation = $context->getConversation(); + + if ($conversation->isInState('survey_feedback')) { + $feedback = $context->getMessage(); + $rating = $conversation->get('rating'); + $name = $conversation->get('name', 'Anonymous'); + + // Clear survey state + $conversation->setState(null); + $conversation->remove('rating'); + + return "Thank you, $name! Your rating ($rating/5) and feedback have been recorded: \"$feedback\""; + } + + return null; // Let other handlers process +}); + +// Fallback for unmatched messages +$bot->fallback(function($context) { + return "I didn't understand that. Type `help` to see what I can do! ๐Ÿค–"; +}); + +// Send response +if ($driver->hasMessage()) { + // Response is automatically sent by the SlackDriver + http_response_code(200); + echo "OK"; +} else { + http_response_code(200); + echo "No message to process"; +} + diff --git a/examples/telegram.php b/examples/telegram.php new file mode 100644 index 0000000..04ae3b2 --- /dev/null +++ b/examples/telegram.php @@ -0,0 +1,174 @@ +middleware(function($context) { + error_log("Telegram message: " . $context->getMessage() . " from: " . $context->getSenderId()); + return true; +}); + +// Handle /start command +$bot->hears(['/start', 'start'], function($context) { + $name = $context->getData()['from']['first_name'] ?? 'there'; + return "Welcome to our bot, $name! ๐Ÿค–\n\nType /help to see what I can do."; +}); + +// Handle /help command +$bot->hears(['/help', 'help'], function($context) { + return [ + "๐Ÿค– *Bot Commands:*", + "", + "โ€ข `/start` - Start the bot", + "โ€ข `/help` - Show this help message", + "โ€ข `my name is [name]` - Tell me your name", + "โ€ข `order` - Start food ordering", + "โ€ข `weather [city]` - Get weather info", + "โ€ข `joke` - Get a random joke", + "โ€ข `/cancel` - Cancel current operation" + ]; +}); + +// Capture user name +$bot->hears('my name is {name}', function($context) { + $name = $context->getParam('name'); + $context->getConversation()->set('user_name', $name); + return "Nice to meet you, $name! ๐Ÿ˜Š\n\nNow I'll remember your name."; +}); + +// Start food ordering +$bot->hears(['order', '/order'], function($context) { + $context->getConversation()->setState('ordering_category'); + return "๐Ÿฝ๏ธ *Food Ordering*\n\nWhat would you like to order?\n\nโ€ข Pizza ๐Ÿ•\nโ€ข Burger ๐Ÿ”\nโ€ข Salad ๐Ÿฅ—\nโ€ข Drinks ๐Ÿฅค"; +}); + +// Handle food category selection +$bot->hears(['pizza', 'burger', 'salad', 'drinks'], function($context) { + $conversation = $context->getConversation(); + + if ($conversation->isInState('ordering_category')) { + $category = ucfirst($context->getMessage()); + $conversation->set('order_category', $category); + $conversation->setState('ordering_quantity'); + + $emoji = ['Pizza' => '๐Ÿ•', 'Burger' => '๐Ÿ”', 'Salad' => '๐Ÿฅ—', 'Drinks' => '๐Ÿฅค']; + + return "Great choice! {$emoji[$category]} $category\n\nHow many would you like? (Enter a number)"; + } + + return null; // Let other handlers process +}); + +// Handle quantity input +$bot->hears('/^\d+$/', function($context) { + $conversation = $context->getConversation(); + + if ($conversation->isInState('ordering_quantity')) { + $quantity = $context->getMessage(); + $category = $conversation->get('order_category'); + $userName = $conversation->get('user_name', 'Customer'); + + $conversation->set('order_quantity', $quantity); + $conversation->setState('ordering_confirm'); + + return "๐Ÿ“‹ *Order Summary:*\n\n" . + "Customer: $userName\n" . + "Item: $category\n" . + "Quantity: $quantity\n\n" . + "Type `confirm` to place order or `cancel` to start over."; + } + + return null; // Let other handlers process +}); + +// Confirm order +$bot->hears(['confirm', '/confirm'], function($context) { + $conversation = $context->getConversation(); + + if ($conversation->isInState('ordering_confirm')) { + $category = $conversation->get('order_category'); + $quantity = $conversation->get('order_quantity'); + $userName = $conversation->get('user_name', 'Customer'); + + // Clear order state but keep user name + $conversation->setState(null); + $conversation->remove('order_category'); + $conversation->remove('order_quantity'); + + return "โœ… *Order Confirmed!*\n\n" . + "Thank you, $userName!\n" . + "Your order of $quantity $category will be ready in 20-30 minutes.\n\n" . + "Order ID: #" . rand(1000, 9999); + } + + return "There's nothing to confirm right now. Type `order` to start ordering!"; +}); + +// Weather command +$bot->hears('weather {city}', function($context) { + $city = $context->getParam('city'); + // In a real implementation, you'd call a weather API + return "๐ŸŒค๏ธ *Weather in " . ucfirst($city) . ":*\n\n" . + "Temperature: 22ยฐC\n" . + "Condition: Partly Cloudy\n" . + "Humidity: 65%\n" . + "Wind: 5 km/h\n\n" . + "_Note: This is a demo response_"; +}); + +// Random joke +$bot->hears(['joke', '/joke'], function($context) { + $jokes = [ + "Why don't programmers like nature? It has too many bugs! ๐Ÿ›", + "How many programmers does it take to change a light bulb? None, that's a hardware problem! ๐Ÿ’ก", + "Why do Java developers wear glasses? Because they can't C#! ๐Ÿ‘“", + "A SQL query goes into a bar, walks up to two tables and asks: 'Can I join you?' ๐Ÿบ", + "Why did the developer go broke? Because he used up all his cache! ๐Ÿ’ฐ" + ]; + + return $jokes[array_rand($jokes)]; +}); + +// Cancel operation +$bot->hears(['/cancel', 'cancel'], function($context) { + $conversation = $context->getConversation(); + $state = $conversation->getState(); + + if ($state) { + $conversation->setState(null); + $conversation->remove('order_category'); + $conversation->remove('order_quantity'); + return "โŒ Operation cancelled. How else can I help you?"; + } + + return "Nothing to cancel! Type /help to see what I can do."; +}); + +// Fallback for unmatched messages +$bot->fallback(function($context) { + return "๐Ÿค” I didn't understand that.\n\nType `/help` to see available commands!"; +}); + +// Listen for incoming messages +$bot->listen(); + +// For webhook mode - just return success +if ($driver->hasMessage()) { + http_response_code(200); + echo "OK"; +} else { + http_response_code(200); + echo "No message to process"; +} diff --git a/src/Drivers/WhatsAppDriver.php b/src/Drivers/WhatsAppDriver.php deleted file mode 100644 index 15d1283..0000000 --- a/src/Drivers/WhatsAppDriver.php +++ /dev/null @@ -1,224 +0,0 @@ -token = $token; - $this->phoneNumberId = $phoneNumberId; - $this->webhook = $webhook ?: json_decode(file_get_contents('php://input'), true); - $this->parseWebhook(); - } - - /** - * Parse WhatsApp webhook data - */ - private function parseWebhook(): void - { - if (!$this->webhook || !isset($this->webhook['entry'])) { - return; - } - - foreach ($this->webhook['entry'] as $entry) { - if (isset($entry['changes'])) { - foreach ($entry['changes'] as $change) { - if (isset($change['value']['messages'])) { - foreach ($change['value']['messages'] as $message) { - $this->message = $message['text']['body'] ?? null; - $this->senderId = $message['from']; - $this->data = $this->webhook; - return; // Process only the first message - } - } - } - } - } - } - - public function getMessage(): ?string - { - return $this->message; - } - - public function getSenderId(): ?string - { - return $this->senderId; - } - - public function sendMessage(string $message, ?string $senderId = null): bool - { - $to = $senderId ?: $this->senderId; - - if (!$to) { - return false; - } - - $url = "https://graph.facebook.com/v17.0/{$this->phoneNumberId}/messages"; - - $data = [ - 'messaging_product' => 'whatsapp', - 'to' => $to, - 'text' => ['body' => $message] - ]; - - $options = [ - 'http' => [ - 'header' => [ - "Content-Type: application/json", - "Authorization: Bearer {$this->token}" - ], - 'method' => 'POST', - 'content' => json_encode($data) - ] - ]; - - $context = stream_context_create($options); - $result = file_get_contents($url, false, $context); - - return $result !== false; - } - - public function getData(): array - { - return $this->data ?? []; - } - - public function hasMessage(): bool - { - return !empty($this->message); - } - - /** - * Send template message - */ - public function sendTemplate(string $templateName, array $parameters = [], ?string $to = null): bool - { - $to = $to ?: $this->senderId; - - if (!$to) { - return false; - } - - $url = "https://graph.facebook.com/v17.0/{$this->phoneNumberId}/messages"; - - $template = [ - 'name' => $templateName, - 'language' => ['code' => 'en_US'] - ]; - - if (!empty($parameters)) { - $template['components'] = [ - [ - 'type' => 'body', - 'parameters' => array_map(function($param) { - return ['type' => 'text', 'text' => $param]; - }, $parameters) - ] - ]; - } - - $data = [ - 'messaging_product' => 'whatsapp', - 'to' => $to, - 'type' => 'template', - 'template' => $template - ]; - - $options = [ - 'http' => [ - 'header' => [ - "Content-Type: application/json", - "Authorization: Bearer {$this->token}" - ], - 'method' => 'POST', - 'content' => json_encode($data) - ] - ]; - - $context = stream_context_create($options); - $result = file_get_contents($url, false, $context); - - return $result !== false; - } - - /** - * Send image - */ - public function sendImage(string $imageUrl, string $caption = '', ?string $to = null): bool - { - $to = $to ?: $this->senderId; - - if (!$to) { - return false; - } - - $url = "https://graph.facebook.com/v17.0/{$this->phoneNumberId}/messages"; - - $data = [ - 'messaging_product' => 'whatsapp', - 'to' => $to, - 'type' => 'image', - 'image' => [ - 'link' => $imageUrl, - 'caption' => $caption - ] - ]; - - $options = [ - 'http' => [ - 'header' => [ - "Content-Type: application/json", - "Authorization: Bearer {$this->token}" - ], - 'method' => 'POST', - 'content' => json_encode($data) - ] - ]; - - $context = stream_context_create($options); - $result = file_get_contents($url, false, $context); - - return $result !== false; - } - - /** - * Mark message as read - */ - public function markAsRead(string $messageId): bool - { - $url = "https://graph.facebook.com/v17.0/{$this->phoneNumberId}/messages"; - - $data = [ - 'messaging_product' => 'whatsapp', - 'status' => 'read', - 'message_id' => $messageId - ]; - - $options = [ - 'http' => [ - 'header' => [ - "Content-Type: application/json", - "Authorization: Bearer {$this->token}" - ], - 'method' => 'POST', - 'content' => json_encode($data) - ] - ]; - - $context = stream_context_create($options); - $result = file_get_contents($url, false, $context); - - return $result !== false; - } -} From 71f8656ccbec8873e4c005cff87f92a124d156f1 Mon Sep 17 00:00:00 2001 From: TusharKhan Date: Wed, 20 Aug 2025 02:36:22 +0600 Subject: [PATCH 2/2] update: remove outdated test results and add portfolio link in README --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index c6a1f3b..075d6eb 100644 --- a/README.md +++ b/README.md @@ -323,8 +323,6 @@ composer cs-check composer cs-fix ``` -**Test Results**: 79 tests, 193 assertions (2 minor errors in advanced conversation features) - ## ๐Ÿ“– Examples & Documentation ### Working Examples @@ -472,3 +470,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file --- Made with โค๏ธ by [Tushar Khan](https://github.com/tusharkhan) +Portfolio: [tusharkhan.me](https://tusharkhan.me) \ No newline at end of file