├── .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 |
--------------------------------------------------------------------------------