├── .gitignore ├── .styleci.yml ├── changelog.md ├── composer.json ├── config └── telegram.php ├── database └── migrations │ ├── 2021_06_14_171118_create_telegram_bot_structure.php │ ├── 2022_02_18_175100_update_to_0.75.0.php │ ├── 2022_04_24_175700_update_to_0.77.0.php │ ├── 2022_10_04_221900_update_to_0.78.0.php │ ├── 2022_11_11_130500_update_to_0.80.0.php │ ├── 2023_05-07_101600_update_to_0.81.0.php │ └── sql │ ├── 0.74.0-0.75.0.sql │ ├── 0.76.1-0.77.0.sql │ ├── 0.77.1-0.78.0.sql │ ├── 0.79.0-0.80.0.sql │ ├── 0.80.0-0.81.0.sql │ └── structure-0.73.0.sql ├── license.md ├── phpunit.xml ├── readme.md ├── routes └── telegram.php └── src ├── Console └── Commands │ ├── GeneratorCommand.php │ ├── MakeTelegramCommand.php │ ├── TelegramCloseCommand.php │ ├── TelegramDeleteWebhookCommand.php │ ├── TelegramFetchCommand.php │ ├── TelegramLogoutCommand.php │ ├── TelegramPublishCommand.php │ ├── TelegramSetWebhookCommand.php │ └── stubs │ ├── example-start-command.stub │ └── telegram-command.stub ├── Facades ├── CallbackButton.php └── Telegram.php ├── Factories └── CallbackButton.php ├── Http └── Middleware │ └── TrustTelegramNetwork.php ├── LaravelTelegramBot.php ├── Telegram ├── Commands │ ├── CallbackqueryCommand.php │ └── GenericmessageCommand.php ├── Conversation │ ├── ConversationWrapper.php │ └── LeadsConversation.php ├── InlineKeyboardButton │ ├── CallbackPayload.php │ └── RemembersCallbackPayload.php └── UsesEffectiveEntities.php └── TelegramServiceProvider.php /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/laravel,composer 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=laravel,composer 4 | 5 | ### Composer ### 6 | composer.phar 7 | /vendor/ 8 | 9 | # Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control 10 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 11 | composer.lock 12 | 13 | ### Laravel ### 14 | node_modules/ 15 | npm-debug.log 16 | yarn-error.log 17 | 18 | # Laravel 4 specific 19 | bootstrap/compiled.php 20 | app/storage/ 21 | 22 | # Laravel 5 & Lumen specific 23 | public/storage 24 | public/hot 25 | 26 | # Laravel 5 & Lumen specific with changed public path 27 | public_html/storage 28 | public_html/hot 29 | 30 | storage/*.key 31 | .env 32 | Homestead.yaml 33 | Homestead.json 34 | /.vagrant 35 | .phpunit.result.cache 36 | 37 | # Laravel IDE helper 38 | *.meta.* 39 | _ide_* 40 | 41 | # End of https://www.toptal.com/developers/gitignore/api/laravel,composer 42 | 43 | /.idea 44 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `LaravelTelegramBot` will be documented in this file. 4 | 5 | ## Version 2.1.0 6 | 7 | ### Added 8 | - Support for Laravel 10 9 | 10 | ### Changed 11 | - Bump to core version 0.81.0 12 | 13 | ## Version 2.0.0 14 | 15 | ### Added 16 | - Everything 17 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "php-telegram-bot/laravel", 3 | "description": "Integrates PHP Telegram Bot into Laravel.", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Avtandil Kikabidze aka LONGMAN", 8 | "email": "akalongman@gmail.com", 9 | "homepage": "http://longman.me", 10 | "role": "Maintainer, Developer" 11 | }, 12 | { 13 | "name": "Tii", 14 | "email": "mail@tii.one", 15 | "role": "Developer" 16 | } 17 | ], 18 | "homepage": "https://github.com/php-telegram-bot/laravel", 19 | "keywords": ["laravel", "telegram", "bot"], 20 | "require": { 21 | "illuminate/support": "~7|~8|~9|~10|~11|~12", 22 | "longman/telegram-bot": "^0.81", 23 | "ext-pcntl": "*" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "~9.0", 27 | "symfony/process": "^5.3" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "PhpTelegramBot\\Laravel\\": "src/" 32 | } 33 | }, 34 | "extra": { 35 | "branch-alias": { 36 | "dev-main": "2.1.x-dev" 37 | }, 38 | "laravel": { 39 | "providers": [ 40 | "PhpTelegramBot\\Laravel\\TelegramServiceProvider" 41 | ], 42 | "aliases": { 43 | "CallbackButton": "PhpTelegramBot\\Laravel\\Facades\\CallbackButton", 44 | "Telegram": "PhpTelegramBot\\Laravel\\Facades\\Telegram" 45 | } 46 | } 47 | }, 48 | "minimum-stability": "dev", 49 | "prefer-stable": true, 50 | "config": { 51 | "allow-plugins": { 52 | "dealerdirect/phpcodesniffer-composer-installer": true 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /config/telegram.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'api_token' => env('TELEGRAM_API_TOKEN'), 6 | 7 | 'username' => env('TELEGRAM_BOT_USERNAME', ''), 8 | 9 | 'api_url' => env('TELEGRAM_API_URL'), 10 | ], 11 | 12 | 'admins' => env('TELEGRAM_ADMINS', '') 13 | 14 | ]; 15 | -------------------------------------------------------------------------------- /database/migrations/2021_06_14_171118_create_telegram_bot_structure.php: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # LaravelTelegramBot 2 | 3 | [![Latest Version on Packagist][ico-version]][link-packagist] 4 | [![Total Downloads][ico-downloads]][link-downloads] 5 | 6 | ## Installation 7 | 8 | Install this package through Composer. Run this command in your project's terminal: 9 | 10 | ``` bash 11 | composer require php-telegram-bot/laravel 12 | ``` 13 | 14 | Execute the following command to publish the folder structure to your Laravel application: 15 | ```bash 16 | php artisan telegram:publish 17 | ``` 18 | This also includes a dummy `/start` command to give you a quick start. 19 | 20 | Since we're using the database part of php-telegram-bot you should run the migrations so the database schema gets installed: 21 | ```bash 22 | php artisan migrate 23 | ``` 24 | 25 | And add the following lines to your .env file: 26 | ```dotenv 27 | TELEGRAM_API_TOKEN= 28 | TELEGRAM_BOT_USERNAME= 29 | TELEGRAM_API_URL= 30 | TELEGRAM_ADMINS= 31 | ``` 32 | 33 | `TELEGRAM_API_TOKEN` and `TELEGRAM_BOT_USERNAME` should be filled with the corresponding data from [@BotFather](https://t.me/BotFather) 34 | 35 | `TELEGRAM_API_URL` is optional and can be filled with the URL to your [custom Bot API Server](https://core.telegram.org/bots/api#using-a-local-bot-api-server) if you want to use one. 36 | 37 | `TELEGRAM_ADMINS` is optional and a comma-separated list of Telegram User IDs that gets passed to the `enableAdmins` command of php-telegram-bot to enable admin commands for those users. 38 | 39 | After that you can run `php artisan telegram:set-webhook` if your development server is reachable from the outside or you're using a custom bot api server. 40 | 41 | Or `php artisan telegram:fetch` to start fetching your updates via polling. 42 | 43 | ⚠️ Be aware that you have to cancel and restart the `telegram:fetch` command, if you change your code. 44 | 45 | ## Usage 46 | For further basic configuration of this Laravel package you do not need to create any configuration files. 47 | 48 | Artisan terminal commands for the Webhook usage (remember, that you need an HTTPS server for it): 49 | ``` bash 50 | # Use this method to specify a url and receive incoming updates via an outgoing webhook 51 | php artisan telegram:set-webhook 52 | # List of available options: 53 | # --d|drop-pending-updates : Drop all pending updates 54 | # --a|all-update-types : Explicitly allow all updates (including "chat_member") 55 | # --allowed-updates= : Define allowed updates (comma-seperated) 56 | 57 | # Use this method to remove webhook integration if you decide to switch back to getUpdates 58 | php artisan telegram:delete-webhook 59 | # List of available options: 60 | # --d|drop-pending-updates : Pass to drop all pending updates 61 | ``` 62 | Artisan terminal commands for the Telegram getUpdates method: 63 | ``` bash 64 | # Fetches Telegram updates periodically 65 | php artisan telegram:fetch 66 | # List of available options: 67 | # --a|all-update-types : Explicitly allow all updates (including "chat_member") 68 | # --allowed-updates= : Define allowed updates (comma-seperated) 69 | ``` 70 | Artisan terminal command for Telegram Server logging out: 71 | ``` bash 72 | # Sends a logout to the currently registered Telegram Server 73 | php artisan telegram:logout 74 | ``` 75 | Artisan terminal command for closing Telegram Server: 76 | ``` bash 77 | # Sends a close to the currently registered Telegram Server 78 | php artisan telegram:close 79 | ``` 80 | Artisan terminal command for publishing Telegram command folder structure in your project: 81 | ``` bash 82 | # Publishes folder structure for Telegram Commands 83 | # Default StartCommand class will be created 84 | php artisan telegram:publish 85 | ``` 86 | Artisan terminal command for creating new Telegram command class in your project: 87 | ``` bash 88 | # Create a new Telegram Bot Command class 89 | # e.g. php artisan make:telegram-command Menu --> will make User command class MenuCommand 90 | # e.g. php artisan make:telegram-command Genericmessage --system --> will make System command class GenericmessageCommand 91 | php artisan make:telegram-command 92 | # List of available options: 93 | # name : Name of the Telegram Command 94 | # --a|admin : Generate a AdminCommand 95 | # --s|system : Generate a SystemCommand 96 | # Without admin or system option default User command will be created 97 | ``` 98 | 99 | 100 | 101 | ## Credits 102 | 103 | - [Avtandil Kikabidze aka LONGMAN](https://github.com/akalongman) 104 | - [TiiFuchs](https://github.com/TiiFuchs) 105 | - [All Contributors][link-contributors] 106 | 107 | ## License 108 | 109 | Please see the [license file](license.md) for more information. 110 | 111 | [ico-version]: https://img.shields.io/packagist/v/php-telegram-bot/laravel.svg?style=flat-square 112 | [ico-downloads]: https://img.shields.io/packagist/dt/php-telegram-bot/laravel.svg?style=flat-square 113 | 114 | [link-packagist]: https://packagist.org/packages/php-telegram-bot/laravel 115 | [link-downloads]: https://packagist.org/packages/php-telegram-bot/laravel 116 | [link-contributors]: https://github.com/php-telegram-bot/laravel/contributors 117 | -------------------------------------------------------------------------------- /routes/telegram.php: -------------------------------------------------------------------------------- 1 | handle(); 9 | })->middleware('telegram.network')->name('telegram.webhook'); 10 | -------------------------------------------------------------------------------- /src/Console/Commands/GeneratorCommand.php: -------------------------------------------------------------------------------- 1 | error("{$basename} already exists!"); 17 | return false; 18 | } 19 | 20 | $content = file_get_contents($source); 21 | $content = $this->replacePlaceholder($content, $replacements); 22 | file_put_contents($destination, $content); 23 | 24 | return true; 25 | } 26 | 27 | protected function replacePlaceholder(string $content, array $replacements): string 28 | { 29 | foreach ($replacements as $from => $to) { 30 | $content = str_replace($from, $to, $content); 31 | } 32 | 33 | return $content; 34 | } 35 | 36 | protected function getRootNamespace(): string 37 | { 38 | return rtrim($this->laravel->getNamespace(), '\\'); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/Console/Commands/MakeTelegramCommand.php: -------------------------------------------------------------------------------- 1 | argument('name'); // start 26 | 27 | if (Str::endsWith($name, ['Command', 'command'])) { 28 | $name = (string) Str::of($name)->substr(0, -7)->lower(); 29 | } else { 30 | $name = Str::lower($name); 31 | } 32 | 33 | $class = Str::studly($name) . 'Command'; 34 | 35 | $success = $this->publish( 36 | __DIR__ . '/stubs/telegram-command.stub', 37 | app_path("Telegram/Commands/{$class}.php"), 38 | [ 39 | 'DummyNamespace' => $this->getRootNamespace(), 40 | 'DummyParent' => $this->getParentClassName(), 41 | 'DummyClass' => $class, 42 | '{{name}}' => $name 43 | ] 44 | ); 45 | 46 | if ($success) { 47 | $this->info('Telegram Command created successfully'); 48 | } 49 | } 50 | 51 | protected function getParentClassName() 52 | { 53 | if ($this->option('admin')) { 54 | return 'AdminCommand'; 55 | } 56 | 57 | if ($this->option('system')) { 58 | return 'SystemCommand'; 59 | } 60 | 61 | return 'UserCommand'; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Console/Commands/TelegramCloseCommand.php: -------------------------------------------------------------------------------- 1 | isOk()) { 20 | $this->error($response->getDescription()); 21 | } 22 | 23 | $this->info($response->getDescription()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Console/Commands/TelegramDeleteWebhookCommand.php: -------------------------------------------------------------------------------- 1 | option('drop-pending-updates')) { 20 | $options['drop_pending_updates'] = true; 21 | } 22 | 23 | $response = Request::deleteWebhook($options); 24 | 25 | if (! $response->isOk()) { 26 | $this->error($response->getDescription()); 27 | } 28 | 29 | $this->info($response->getDescription()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Console/Commands/TelegramFetchCommand.php: -------------------------------------------------------------------------------- 1 | callSilent('telegram:delete-webhook'); 30 | 31 | $options = [ 32 | 'timeout' => 30 33 | ]; 34 | 35 | // allowed_updates 36 | if ($this->option('all-update-types')) { 37 | $options['allowed_updates'] = Update::getUpdateTypes(); 38 | } elseif ($allowedUpdates = $this->option('allowed-updates')) { 39 | $options['allowed_updates'] = Str::of($allowedUpdates)->explode(','); 40 | } 41 | 42 | $this->info("Start fetching updates...\n(Exit with Ctrl + C)"); 43 | 44 | if ($this->childPid = pcntl_fork()) { 45 | // Parent process 46 | 47 | while (true) { 48 | 49 | if ($this->shallExit) { 50 | exec('kill -9 ' . $this->childPid); 51 | break; 52 | } 53 | 54 | } 55 | 56 | } else { 57 | // Child process 58 | 59 | while (true) { 60 | 61 | $response = rescue(fn() => $bot->handleGetUpdates($options)); 62 | 63 | if ($response !== null && ! $response->isOk()) { 64 | $this->error($response->getDescription()); 65 | } 66 | 67 | } 68 | 69 | } 70 | } 71 | 72 | public function getSubscribedSignals(): array 73 | { 74 | return [SIGINT]; 75 | } 76 | 77 | public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false 78 | { 79 | $this->shallExit = true; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Console/Commands/TelegramLogoutCommand.php: -------------------------------------------------------------------------------- 1 | isOk()) { 20 | $this->error($response->getDescription()); 21 | } 22 | 23 | $this->info($response->getDescription()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Console/Commands/TelegramPublishCommand.php: -------------------------------------------------------------------------------- 1 | ensureDirectoryExists(app_path('Telegram/Commands')); 16 | $success = $this->publish( 17 | __DIR__ . '/stubs/example-start-command.stub', 18 | app_path('Telegram/Commands/StartCommand.php'), 19 | [ 20 | 'DummyRootNamespace' => $this->getRootNamespace(), 21 | ] 22 | ); 23 | 24 | if ($success) { 25 | $this->info('Publishing complete.'); 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/Console/Commands/TelegramSetWebhookCommand.php: -------------------------------------------------------------------------------- 1 | argument('hostname'); 23 | if (! $hostname) { 24 | $hostname = $this->ask('Which hostname do you like to set?', config('app.url')); 25 | } 26 | 27 | if (! Str::of($hostname)->startsWith('http')) { 28 | $schema = match (app()->environment()) { 29 | 'local' => 'http', 30 | default => 'https' 31 | }; 32 | $hostname = "{$schema}://{$hostname}"; 33 | } 34 | 35 | $url = $hostname . route('telegram.webhook', [ 36 | 'token' => config('telegram.bot.api_token') 37 | ], false); 38 | 39 | $options = []; 40 | if ($this->option('drop-pending-updates')) { 41 | $options['drop_pending_updates'] = true; 42 | } 43 | 44 | if ($this->option('all-update-types')) { 45 | $options['allowed_updates'] = Update::getUpdateTypes(); 46 | } elseif ($allowedUpdates = $this->option('allowed-updates')) { 47 | $options['allowed_updates'] = Str::of($allowedUpdates)->explode(','); 48 | } 49 | 50 | $response = $bot->setWebhook($url, $options); 51 | 52 | if (! $response->isOk()) { 53 | $this->error($response->getDescription()); 54 | } 55 | 56 | $this->info("Telegram Webhook set to {$url}"); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Console/Commands/stubs/example-start-command.stub: -------------------------------------------------------------------------------- 1 | replyToChat('Hello world! 👋'); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/Console/Commands/stubs/telegram-command.stub: -------------------------------------------------------------------------------- 1 | withXxxxx($value) 20 | if (Str::startsWith($name, 'with')) { 21 | $key = (string) Str::of($name)->after('with')->snake(); 22 | $value = head($arguments); 23 | 24 | return $this->with($key, $value); 25 | } 26 | 27 | throw new \BadMethodCallException("Call to undefined method CallbackButton::{$name}()"); 28 | } 29 | 30 | public function with(string $key, mixed $value): self 31 | { 32 | $this->data[$key] = $value; 33 | return $this; 34 | } 35 | 36 | public function new(): self 37 | { 38 | return clone $this; 39 | } 40 | 41 | public function returnTo(string $className): self 42 | { 43 | $this->className = $className; 44 | return $this; 45 | } 46 | 47 | public function make(string $text, array $payload = []): InlineKeyboardButton 48 | { 49 | // Find valid hash 50 | do { 51 | $hash = Str::random(32); 52 | $cacheKey = 'CallbackQuery:'.$hash; 53 | } while (Cache::has($cacheKey)); 54 | 55 | // Save payload 56 | $payload = $payload + $this->data; 57 | if (isset($this->className)) { 58 | $payload['__class'] = $this->className; 59 | } 60 | Cache::put($cacheKey, $payload); 61 | 62 | // Assemble button 63 | return new InlineKeyboardButton([ 64 | 'text' => $text, 65 | 'callback_data' => $hash 66 | ]); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/Http/Middleware/TrustTelegramNetwork.php: -------------------------------------------------------------------------------- 1 | ip(), $this->localIpNets)) { 43 | return $next($request); 44 | } 45 | 46 | if (IpUtils::checkIp($request->ip(), $this->trustedIpNets)) { 47 | return $next($request); 48 | } 49 | 50 | abort(403); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/LaravelTelegramBot.php: -------------------------------------------------------------------------------- 1 | callbacks[] = $callback; 17 | } 18 | 19 | public function call(Update $update): ?ServerResponse 20 | { 21 | foreach ($this->callbacks as $callback) { 22 | $return = $callback($update); 23 | 24 | if ($return instanceof ServerResponse) { 25 | return $return; 26 | } elseif ($return === true) { 27 | return Request::emptyResponse(); 28 | } 29 | } 30 | 31 | return null; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/Telegram/Commands/CallbackqueryCommand.php: -------------------------------------------------------------------------------- 1 | getUpdate()); 29 | if ($return instanceof ServerResponse) { 30 | return $return; 31 | } 32 | 33 | // Check if we have data for that hash in the Cache 34 | if ($class = $this->payload()?->get('__class')) { 35 | if (class_exists($class) && is_subclass_of($class, Command::class)) { 36 | /** @var Command $command */ 37 | $command = new $class($this->telegram, $this->update); 38 | return $command->preExecute(); 39 | } 40 | } 41 | 42 | // Check if conversation is active 43 | $user = $this->getEffectiveUser($this->getUpdate()); 44 | $chat = $this->getEffectiveChat($this->getUpdate()); 45 | 46 | $conversation = new Conversation( 47 | user_id: $user->getId(), 48 | chat_id: $chat->getId() 49 | ); 50 | 51 | if ($conversation->exists() && ($command = $conversation->getCommand())) { 52 | return $this->getTelegram()->executeCommand($command); 53 | } 54 | 55 | // Check if own CallbackqueryCommand class is available 56 | $class = App::getNamespace() . 'Telegram\\Commands\\CallbackqueryCommand'; 57 | if (class_exists($class) && is_subclass_of($class, SystemCommand::class)) { 58 | /** @var SystemCommand $command */ 59 | $command = new $class($this->telegram, $this->update); 60 | return $command->preExecute(); 61 | } 62 | 63 | return Request::emptyResponse(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Telegram/Commands/GenericmessageCommand.php: -------------------------------------------------------------------------------- 1 | getUpdate()); 25 | if ($return instanceof ServerResponse) { 26 | return $return; 27 | } 28 | 29 | $user = $this->getEffectiveUser($this->getUpdate()); 30 | $chat = $this->getEffectiveChat($this->getUpdate()); 31 | 32 | // Check Conversation 33 | $conversation = new Conversation( 34 | user_id: $user->getId(), 35 | chat_id: $chat->getId() 36 | ); 37 | 38 | if ($conversation->exists() && ($command = $conversation->getCommand())) { 39 | return $this->getTelegram()->executeCommand($command); 40 | } 41 | 42 | // Check if own GenericmessageCommand class is available 43 | $class = App::getNamespace() . 'Telegram\\Commands\\GenericmessageCommand'; 44 | if (class_exists($class) && is_subclass_of($class, SystemCommand::class)) { 45 | /** @var SystemCommand $command */ 46 | $command = new $class($this->telegram, $this->update); 47 | return $command->preExecute(); 48 | } 49 | 50 | return Request::emptyResponse(); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/Telegram/Conversation/ConversationWrapper.php: -------------------------------------------------------------------------------- 1 | getMessage() ?? $update->getEditedMessage()) { 17 | $user = $message->getFrom(); 18 | $chat = $message->getChat(); 19 | } elseif ($callbackQuery = $update->getCallbackQuery()) { 20 | $user = $callbackQuery->getFrom(); 21 | $chat = $callbackQuery->getMessage()?->getChat(); 22 | } 23 | 24 | // TODO: Use getEffective*() Methods that should be created in \Bot Facade 25 | 26 | if (! isset($user) || ! isset($chat)) { 27 | throw new \InvalidArgumentException('Could not determine user or chat for ConversationWrapper'); 28 | } 29 | 30 | $this->conversation = new Conversation( 31 | user_id: $user->getId(), 32 | chat_id: $chat->getId(), 33 | command: $command 34 | ); 35 | 36 | $notes = &$this->conversation->notes; 37 | $notes['vars'] ??= []; 38 | $notes['persist'] ??= []; 39 | 40 | if ($this->conversation->exists()) { 41 | // Remove temporary variables 42 | foreach ($notes['vars'] as $key => $value) { 43 | if (array_search($key, $notes['persist']) === false) { 44 | // Is temporary 45 | $this->temporary[$key] = $value; 46 | unset($notes['vars'][$key]); 47 | } 48 | } 49 | $this->conversation->update(); 50 | } 51 | } 52 | 53 | public function all(): array 54 | { 55 | return $this->conversation->notes['vars'] + $this->temporary; 56 | } 57 | 58 | public function get(string $key, string $default = null): mixed 59 | { 60 | return data_get($this->conversation->notes['vars'], $key) 61 | ?? data_get($this->temporary, $key, $default); 62 | } 63 | 64 | public function has(string $key): bool 65 | { 66 | return $this->get($key) !== null; 67 | } 68 | 69 | public function getConversation(): Conversation 70 | { 71 | return $this->conversation; 72 | } 73 | 74 | public function persist(array $data): self 75 | { 76 | $notes = &$this->conversation->notes; 77 | foreach ($data as $key => $value) { 78 | $notes['vars'][$key] = $value; 79 | $notes['persist'][] = $key; 80 | } 81 | $this->conversation->update(); 82 | 83 | return $this; 84 | } 85 | 86 | public function remember(array $data = [], bool $keepPreviousData = false): self 87 | { 88 | $notes = &$this->conversation->notes; 89 | 90 | if ($keepPreviousData) { 91 | foreach ($this->temporary as $key => $value) { 92 | $notes['vars'][$key] = $value; 93 | } 94 | } 95 | 96 | foreach ($data as $key => $value) { 97 | $notes['vars'][$key] = $value; 98 | $index = array_search($key, $notes['persist']); 99 | if ($index !== false) { 100 | unset($notes['persist'][$index]); 101 | } 102 | } 103 | 104 | $notes['persist'] = array_values($notes['persist']); 105 | $this->conversation->update(); 106 | return $this; 107 | } 108 | 109 | public function exists(): bool 110 | { 111 | return $this->conversation->exists(); 112 | } 113 | 114 | public function end(): void 115 | { 116 | $this->conversation->stop(); 117 | } 118 | 119 | public function cancel(): void 120 | { 121 | $this->conversation->cancel(); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/Telegram/Conversation/LeadsConversation.php: -------------------------------------------------------------------------------- 1 | conversation)) { 30 | $this->conversation = new ConversationWrapper($this->getUpdate(), $this->getName()); 31 | } 32 | 33 | if (isset($key)) { 34 | return $this->conversation->get($key, $default); 35 | } 36 | 37 | return $this->conversation; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/Telegram/InlineKeyboardButton/CallbackPayload.php: -------------------------------------------------------------------------------- 1 | payload; 17 | } 18 | 19 | public function get(string $key, string $default = null): mixed 20 | { 21 | return data_get($this->payload, $key, $default); 22 | } 23 | 24 | public function has(string $key): bool 25 | { 26 | return $this->get($key) !== null; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/Telegram/InlineKeyboardButton/RemembersCallbackPayload.php: -------------------------------------------------------------------------------- 1 | payload)) { 31 | $update = $this->getUpdate(); 32 | $data = $update?->getCallbackQuery()?->getData(); 33 | 34 | if ($data === null) { 35 | return null; 36 | } 37 | 38 | // TODO: Move CacheKey and initialization in CallbackPayload::__construct() 39 | $cacheKey = 'CallbackQuery:'.$data; 40 | 41 | if (! Cache::has($cacheKey)) { 42 | return null; 43 | } 44 | 45 | $payload = Cache::get($cacheKey); 46 | $this->payload = new CallbackPayload($payload); 47 | } 48 | 49 | if (isset($key)) { 50 | return $this->payload->get($key, $default); 51 | } 52 | 53 | return $this->payload; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/Telegram/UsesEffectiveEntities.php: -------------------------------------------------------------------------------- 1 | getUpdateType(); 16 | 17 | $user = $update->$type['from'] 18 | ?? $update->poll_answer['user'] 19 | ?? null; 20 | 21 | return $user ? new User($user) : null; 22 | } 23 | 24 | protected function getEffectiveChat(Update $update): ?Chat 25 | { 26 | $type = $update->getUpdateType(); 27 | 28 | $chat = $update->$type['chat'] 29 | ?? $update->callback_query['message']['chat'] 30 | ?? null; 31 | 32 | return $chat ? new Chat($chat) : null; 33 | } 34 | 35 | protected function getEffectiveMessage(Update $update): ?Message 36 | { 37 | $message = $update->edited_channel_post 38 | ?? $update->channel_post 39 | ?? $update->callback_query['message'] 40 | ?? $update->edited_message 41 | ?? $update->message 42 | ?? null; 43 | 44 | return $message ? new Message($message) : null; 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/TelegramServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadMigrationsFrom(__DIR__ . '/../database/migrations'); 34 | 35 | if (file_exists(base_path('routes/telegram.php'))) { 36 | $this->loadRoutesFrom(base_path('routes/telegram.php')); 37 | } else { 38 | $this->loadRoutesFrom(__DIR__ . '/../routes/telegram.php'); 39 | } 40 | 41 | $router = $this->app->make(Router::class); 42 | $router->aliasMiddleware('telegram.network', TrustTelegramNetwork::class); 43 | 44 | // Publishing is only necessary when using the CLI. 45 | if ($this->app->runningInConsole()) { 46 | $this->bootForConsole(); 47 | } 48 | } 49 | 50 | /** 51 | * Register any package services. 52 | * 53 | * @return void 54 | */ 55 | public function register(): void 56 | { 57 | $this->mergeConfigFrom(__DIR__ . '/../config/telegram.php', 'telegram'); 58 | 59 | $this->configureTelegramBot(); 60 | } 61 | 62 | protected function configureTelegramBot() 63 | { 64 | $token = config('telegram.bot.api_token'); 65 | 66 | if (! $token) { 67 | return; 68 | } 69 | 70 | $username = config('telegram.bot.username'); 71 | 72 | $apiUrl = config('telegram.bot.api_url', ''); 73 | if (! empty($apiUrl)) { 74 | Request::setCustomBotApiUri($apiUrl); 75 | } 76 | 77 | $bot = new Telegram($token, $username); 78 | 79 | // Commands Discovery 80 | $this->discoverTelegramCommands($bot); 81 | $bot->addCommandClass(CallbackqueryCommand::class); 82 | $bot->addCommandClass(GenericmessageCommand::class); 83 | 84 | // Set MySQL Connection 85 | $connection = app('db')->connection('mysql'); 86 | $bot->enableExternalMySql($connection->getPdo(), 'bot_'); 87 | 88 | // Register admins 89 | $this->registerTelegramAdmins($bot); 90 | 91 | $this->app->instance(Telegram::class, $bot); 92 | } 93 | 94 | /** 95 | * Console-specific booting. 96 | * 97 | * @return void 98 | */ 99 | protected function bootForConsole(): void 100 | { 101 | // Publishing the configuration file. 102 | $this->publishes([ 103 | __DIR__ . '/../config/telegram.php' => config_path('telegram.php'), 104 | ], 'telegram-config'); 105 | 106 | $this->publishes([ 107 | __DIR__ . '/../routes/telegram.php' => base_path('routes/telegram.php') 108 | ], 'telegram-routes'); 109 | 110 | // Registering package commands. 111 | $this->commands([ 112 | MakeTelegramCommand::class, 113 | TelegramCloseCommand::class, 114 | TelegramDeleteWebhookCommand::class, 115 | TelegramFetchCommand::class, 116 | TelegramLogoutCommand::class, 117 | TelegramPublishCommand::class, 118 | TelegramSetWebhookCommand::class, 119 | ]); 120 | } 121 | 122 | /** 123 | * @param Telegram $bot 124 | * @throws \ReflectionException 125 | */ 126 | protected function discoverTelegramCommands(Telegram $bot): void 127 | { 128 | $namespace = $this->app->getNamespace(); 129 | $commandsPath = app_path('Telegram/Commands'); 130 | File::ensureDirectoryExists($commandsPath); 131 | 132 | foreach ((new Finder)->in($commandsPath)->files() as $command) { 133 | $command = $namespace . str_replace( 134 | ['/', '.php'], 135 | ['\\', ''], 136 | \Str::after($command->getRealPath(), realpath(app_path()) . DIRECTORY_SEPARATOR) 137 | ); 138 | 139 | if (is_subclass_of($command, Command::class) && 140 | ! (new \ReflectionClass($command))->isAbstract()) { 141 | $bot->addCommandClass($command); 142 | } 143 | } 144 | } 145 | 146 | /** 147 | * @param Telegram $bot 148 | */ 149 | protected function registerTelegramAdmins(Telegram $bot): void 150 | { 151 | $admins = config('telegram.admins', ''); 152 | if (! empty($admins)) { 153 | $admins = explode(',', $admins); 154 | $bot->enableAdmins($admins); 155 | } 156 | } 157 | } 158 | --------------------------------------------------------------------------------