Completed
Push — develop ( ce8452...cb9868 )
by Armando
30s queued 13s
created

Telegram::getCommandObject()   C

Complexity

Conditions 12
Paths 35

Size

Total Lines 44
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 24.6381

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 12
eloc 27
c 2
b 0
f 0
nc 35
nop 2
dl 0
loc 44
ccs 15
cts 27
cp 0.5556
crap 24.6381
rs 6.9666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file is part of the TelegramBot package.
5
 *
6
 * (c) Avtandil Kikabidze aka LONGMAN <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Longman\TelegramBot;
13
14 1
defined('TB_BASE_PATH') || define('TB_BASE_PATH', __DIR__);
15 1
defined('TB_BASE_COMMANDS_PATH') || define('TB_BASE_COMMANDS_PATH', TB_BASE_PATH . '/Commands');
16
17
use Exception;
18
use Longman\TelegramBot\Commands\AdminCommand;
19
use Longman\TelegramBot\Commands\Command;
20
use Longman\TelegramBot\Commands\SystemCommand;
21
use Longman\TelegramBot\Commands\UserCommand;
22
use Longman\TelegramBot\Entities\ServerResponse;
23
use Longman\TelegramBot\Entities\Update;
24
use Longman\TelegramBot\Exception\TelegramException;
25
use PDO;
26
use RecursiveDirectoryIterator;
27
use RecursiveIteratorIterator;
28
use RegexIterator;
29
30
class Telegram
31
{
32
    /**
33
     * Version
34
     *
35
     * @var string
36
     */
37
    protected $version = '0.71.0';
38
39
    /**
40
     * Telegram API key
41
     *
42
     * @var string
43
     */
44
    protected $api_key = '';
45
46
    /**
47
     * Telegram Bot username
48
     *
49
     * @var string
50
     */
51
    protected $bot_username = '';
52
53
    /**
54
     * Telegram Bot id
55
     *
56
     * @var int
57
     */
58
    protected $bot_id = 0;
59
60
    /**
61
     * Raw request data (json) for webhook methods
62
     *
63
     * @var string
64
     */
65
    protected $input = '';
66
67
    /**
68
     * Custom commands paths
69
     *
70
     * @var array
71
     */
72
    protected $commands_paths = [];
73
74
    /**
75
     * Custom commands objects
76
     *
77
     * @var array
78
     */
79
    protected $commands_objects = [];
80
81
    /**
82
     * Current Update object
83
     *
84
     * @var Update
85
     */
86
    protected $update;
87
88
    /**
89
     * Upload path
90
     *
91
     * @var string
92
     */
93
    protected $upload_path = '';
94
95
    /**
96
     * Download path
97
     *
98
     * @var string
99
     */
100
    protected $download_path = '';
101
102
    /**
103
     * MySQL integration
104
     *
105
     * @var bool
106
     */
107
    protected $mysql_enabled = false;
108
109
    /**
110
     * PDO object
111
     *
112
     * @var PDO
113
     */
114
    protected $pdo;
115
116
    /**
117
     * Commands config
118
     *
119
     * @var array
120
     */
121
    protected $commands_config = [];
122
123
    /**
124
     * Admins list
125
     *
126
     * @var array
127
     */
128
    protected $admins_list = [];
129
130
    /**
131
     * ServerResponse of the last Command execution
132
     *
133
     * @var ServerResponse
134
     */
135
    protected $last_command_response;
136
137
    /**
138
     * Check if runCommands() is running in this session
139
     *
140
     * @var bool
141
     */
142
    protected $run_commands = false;
143
144
    /**
145
     * Is running getUpdates without DB enabled
146
     *
147
     * @var bool
148
     */
149
    protected $getupdates_without_database = false;
150
151
    /**
152
     * Last update ID
153
     * Only used when running getUpdates without a database
154
     *
155
     * @var int
156
     */
157
    protected $last_update_id;
158
159
    /**
160
     * The command to be executed when there's a new message update and nothing more suitable is found
161
     */
162
    public const GENERIC_MESSAGE_COMMAND = 'genericmessage';
163
164
    /**
165
     * The command to be executed by default (when no other relevant commands are applicable)
166
     */
167
    public const GENERIC_COMMAND = 'generic';
168
169
    /**
170
     * Update filter
171
     * Filter updates
172
     *
173
     * @var callback
174
     */
175
    protected $update_filter;
176
177
    /**
178
     * Telegram constructor.
179
     *
180
     * @param string $api_key
181
     * @param string $bot_username
182
     *
183
     * @throws TelegramException
184
     */
185 31
    public function __construct(string $api_key, string $bot_username = '')
186
    {
187 31
        if (empty($api_key)) {
188 1
            throw new TelegramException('API KEY not defined!');
189
        }
190 31
        preg_match('/(\d+):[\w\-]+/', $api_key, $matches);
191 31
        if (!isset($matches[1])) {
192 1
            throw new TelegramException('Invalid API KEY defined!');
193
        }
194 31
        $this->bot_id  = (int) $matches[1];
195 31
        $this->api_key = $api_key;
196
197 31
        $this->bot_username = $bot_username;
198
199
        //Add default system commands path
200 31
        $this->addCommandsPath(TB_BASE_COMMANDS_PATH . '/SystemCommands');
201
202 31
        Request::initialize($this);
203 31
    }
204
205
    /**
206
     * Initialize Database connection
207
     *
208
     * @param array  $credentials
209
     * @param string $table_prefix
210
     * @param string $encoding
211
     *
212
     * @return Telegram
213
     * @throws TelegramException
214
     */
215 9
    public function enableMySql(array $credentials, string $table_prefix = '', string $encoding = 'utf8mb4'): Telegram
216
    {
217 9
        $this->pdo = DB::initialize($credentials, $this, $table_prefix, $encoding);
218 9
        ConversationDB::initializeConversation();
219 9
        $this->mysql_enabled = true;
220
221 9
        return $this;
222
    }
223
224
    /**
225
     * Initialize Database external connection
226
     *
227
     * @param PDO    $external_pdo_connection PDO database object
228
     * @param string $table_prefix
229
     *
230
     * @return Telegram
231
     * @throws TelegramException
232
     */
233
    public function enableExternalMySql(PDO $external_pdo_connection, string $table_prefix = ''): Telegram
234
    {
235
        $this->pdo = DB::externalInitialize($external_pdo_connection, $this, $table_prefix);
236
        ConversationDB::initializeConversation();
237
        $this->mysql_enabled = true;
238
239
        return $this;
240
    }
241
242
    /**
243
     * Get commands list
244
     *
245
     * @return array $commands
246
     * @throws TelegramException
247
     */
248 1
    public function getCommandsList(): array
249
    {
250 1
        $commands = [];
251
252 1
        foreach ($this->commands_paths as $path) {
253
            try {
254
                //Get all "*Command.php" files
255 1
                $files = new RegexIterator(
256 1
                    new RecursiveIteratorIterator(
257 1
                        new RecursiveDirectoryIterator($path)
258
                    ),
259 1
                    '/^.+Command.php$/'
260
                );
261
262 1
                foreach ($files as $file) {
263
                    //Remove "Command.php" from filename
264 1
                    $command      = $this->sanitizeCommand(substr($file->getFilename(), 0, -11));
265 1
                    $command_name = mb_strtolower($command);
266
267 1
                    if (array_key_exists($command_name, $commands)) {
268
                        continue;
269
                    }
270
271 1
                    require_once $file->getPathname();
272
273 1
                    $command_obj = $this->getCommandObject($command, $file->getPathname());
274 1
                    if ($command_obj instanceof Command) {
275 1
                        $commands[$command_name] = $command_obj;
276
                    }
277
                }
278
            } catch (Exception $e) {
279
                throw new TelegramException('Error getting commands from path: ' . $path, $e);
0 ignored issues
show
Bug introduced by
$e of type Exception is incompatible with the type integer expected by parameter $code of Longman\TelegramBot\Exce...xception::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

279
                throw new TelegramException('Error getting commands from path: ' . $path, /** @scrutinizer ignore-type */ $e);
Loading history...
280
            }
281
        }
282
283 1
        return $commands;
284
    }
285
286
    /**
287
     * Get an object instance of the passed command
288
     *
289
     * @param string $command
290
     * @param string $filepath
291
     *
292
     * @return Command|null
293
     */
294 1
    public function getCommandObject(string $command, string $filepath = ''): ?Command
295
    {
296 1
        if (isset($this->commands_objects[$command])) {
297
            return $this->commands_objects[$command];
298
        }
299
300 1
        $which = ['System'];
301 1
        $this->isAdmin() && $which[] = 'Admin';
302 1
        $which[] = 'User';
303
304 1
        foreach ($which as $auth) {
305 1
            if ($filepath) {
306 1
                $command_namespace = $this->getFileNamespace($filepath);
307
            } else {
308
                $command_namespace = __NAMESPACE__ . '\\Commands\\' . $auth . 'Commands';
309
            }
310 1
            $command_class = $command_namespace . '\\' . $this->ucFirstUnicode($command) . 'Command';
311
312 1
            if (class_exists($command_class)) {
313 1
                $command_obj = new $command_class($this, $this->update);
314
315 1
                switch ($auth) {
316 1
                    case 'System':
317 1
                        if ($command_obj instanceof SystemCommand) {
318 1
                            return $command_obj;
319
                        }
320
                        break;
321
322
                    case 'Admin':
323
                        if ($command_obj instanceof AdminCommand) {
324
                            return $command_obj;
325
                        }
326
                        break;
327
328
                    case 'User':
329
                        if ($command_obj instanceof UserCommand) {
330
                            return $command_obj;
331
                        }
332
                        break;
333
                }
334
            }
335
        }
336
337
        return null;
338
    }
339
340
    /**
341
     * Get namespace from php file by src path
342
     *
343
     * @param string $src (absolute path to file)
344
     *
345
     * @return string|null ("Longman\TelegramBot\Commands\SystemCommands" for example)
346
     */
347 1
    protected function getFileNamespace(string $src): ?string
348
    {
349 1
        $content = file_get_contents($src);
350 1
        if (preg_match('#^namespace\s+(.+?);#m', $content, $m)) {
351 1
            return $m[1];
352
        }
353
354
        return null;
355
    }
356
357
    /**
358
     * Set custom input string for debug purposes
359
     *
360
     * @param string $input (json format)
361
     *
362
     * @return Telegram
363
     */
364
    public function setCustomInput(string $input): Telegram
365
    {
366
        $this->input = $input;
367
368
        return $this;
369
    }
370
371
    /**
372
     * Get custom input string for debug purposes
373
     *
374
     * @return string
375
     */
376
    public function getCustomInput(): string
377
    {
378
        return $this->input;
379
    }
380
381
    /**
382
     * Get the ServerResponse of the last Command execution
383
     *
384
     * @return ServerResponse
385
     */
386
    public function getLastCommandResponse(): ServerResponse
387
    {
388
        return $this->last_command_response;
389
    }
390
391
    /**
392
     * Handle getUpdates method
393
     *
394
     * @todo Remove backwards compatibility for old signature and force $data to be an array.
395
     *
396
     * @param array|int|null $data
397
     * @param int|null       $timeout
398
     *
399
     * @return ServerResponse
400
     * @throws TelegramException
401
     */
402
    public function handleGetUpdates($data = null, ?int $timeout = null): ServerResponse
403
    {
404
        if (empty($this->bot_username)) {
405
            throw new TelegramException('Bot Username is not defined!');
406
        }
407
408
        if (!DB::isDbConnected() && !$this->getupdates_without_database) {
409
            return new ServerResponse(
410
                [
411
                    'ok'          => false,
412
                    'description' => 'getUpdates needs MySQL connection! (This can be overridden - see documentation)',
413
                ],
414
                $this->bot_username
415
            );
416
        }
417
418
        $offset = 0;
419
        $limit  = null;
420
421
        // By default, get update types sent by Telegram.
422
        $allowed_updates = [];
423
424
        // @todo Backwards compatibility for old signature, remove in next version.
425
        if (!is_array($data)) {
426
            $limit = $data;
427
428
            @trigger_error(
429
                sprintf('Use of $limit and $timeout parameters in %s is deprecated. Use $data array instead.', __METHOD__),
430
                E_USER_DEPRECATED
431
            );
432
        } else {
433
            $offset          = $data['offset'] ?? $offset;
434
            $limit           = $data['limit'] ?? $limit;
435
            $timeout         = $data['timeout'] ?? $timeout;
436
            $allowed_updates = $data['allowed_updates'] ?? $allowed_updates;
437
        }
438
439
        // Take custom input into account.
440
        if ($custom_input = $this->getCustomInput()) {
441
            try {
442
                $input = json_decode($this->input, true, 512, JSON_THROW_ON_ERROR);
443
                if (empty($input)) {
444
                    throw new TelegramException('Custom input is empty');
445
                }
446
                $response = new ServerResponse($input, $this->bot_username);
447
            } catch (\Throwable $e) {
448
                throw new TelegramException('Invalid custom input JSON: ' . $e->getMessage());
449
            }
450
        } else {
451
            if (DB::isDbConnected() && $last_update = DB::selectTelegramUpdate(1)) {
452
                // Get last Update id from the database.
453
                $last_update          = reset($last_update);
454
                $this->last_update_id = $last_update['id'] ?? null;
455
            }
456
457
            if ($this->last_update_id !== null) {
458
                $offset = $this->last_update_id + 1; // As explained in the telegram bot API documentation.
459
            }
460
461
            $response = Request::getUpdates(compact('offset', 'limit', 'timeout', 'allowed_updates'));
462
        }
463
464
        if ($response->isOk()) {
465
            // Log update.
466
            TelegramLog::update($response->toJson());
467
468
            // Process all updates
469
            /** @var Update $update */
470
            foreach ($response->getResult() as $update) {
471
                $this->processUpdate($update);
472
            }
473
474
            if (!DB::isDbConnected() && !$custom_input && $this->last_update_id !== null && $offset === 0) {
475
                // Mark update(s) as read after handling
476
                $offset = $this->last_update_id + 1;
477
                $limit  = 1;
478
479
                Request::getUpdates(compact('offset', 'limit', 'timeout', 'allowed_updates'));
480
            }
481
        }
482
483
        return $response;
484
    }
485
486
    /**
487
     * Handle bot request from webhook
488
     *
489
     * @return bool
490
     *
491
     * @throws TelegramException
492
     */
493
    public function handle(): bool
494
    {
495
        if ($this->bot_username === '') {
496
            throw new TelegramException('Bot Username is not defined!');
497
        }
498
499
        $input = Request::getInput();
500
        if (empty($input)) {
501
            throw new TelegramException('Input is empty! The webhook must not be called manually, only by Telegram.');
502
        }
503
504
        // Log update.
505
        TelegramLog::update($input);
506
507
        $post = json_decode($input, true);
508
        if (empty($post)) {
509
            throw new TelegramException('Invalid input JSON! The webhook must not be called manually, only by Telegram.');
510
        }
511
512
        if ($response = $this->processUpdate(new Update($post, $this->bot_username))) {
513
            return $response->isOk();
514
        }
515
516
        return false;
517
    }
518
519
    /**
520
     * Get the command name from the command type
521
     *
522
     * @param string $type
523
     *
524
     * @return string
525
     */
526
    protected function getCommandFromType(string $type): string
527
    {
528
        return $this->ucFirstUnicode(str_replace('_', '', $type));
529
    }
530
531
    /**
532
     * Process bot Update request
533
     *
534
     * @param Update $update
535
     *
536
     * @return ServerResponse
537
     * @throws TelegramException
538
     */
539 1
    public function processUpdate(Update $update): ServerResponse
540
    {
541 1
        $this->update         = $update;
542 1
        $this->last_update_id = $update->getUpdateId();
543
544 1
        if (is_callable($this->update_filter)) {
545 1
            $reason = 'Update denied by update_filter';
546
            try {
547 1
                $allowed = (bool) call_user_func_array($this->update_filter, [$update, $this, &$reason]);
548
            } catch (\Exception $e) {
549
                $allowed = false;
550
            }
551
552 1
            if (!$allowed) {
553 1
                TelegramLog::debug($reason);
554 1
                return new ServerResponse(['ok' => false, 'description' => 'denied']);
555
            }
556
        }
557
558
        //Load admin commands
559
        if ($this->isAdmin()) {
560
            $this->addCommandsPath(TB_BASE_COMMANDS_PATH . '/AdminCommands', false);
561
        }
562
563
        //Make sure we have an up-to-date command list
564
        //This is necessary to "require" all the necessary command files!
565
        $this->commands_objects = $this->getCommandsList();
566
567
        //If all else fails, it's a generic message.
568
        $command = self::GENERIC_MESSAGE_COMMAND;
569
570
        $update_type = $this->update->getUpdateType();
571
        if ($update_type === 'message') {
572
            $message = $this->update->getMessage();
573
            $type    = $message->getType();
574
575
            // Let's check if the message object has the type field we're looking for...
576
            $command_tmp = $type === 'command' ? $message->getCommand() : $this->getCommandFromType($type);
577
            // ...and if a fitting command class is available.
578
            $command_obj = $command_tmp ? $this->getCommandObject($command_tmp) : null;
579
580
            // Empty usage string denotes a non-executable command.
581
            // @see https://github.com/php-telegram-bot/core/issues/772#issuecomment-388616072
582
            if (
583
                ($command_obj === null && $type === 'command')
584
                || ($command_obj !== null && $command_obj->getUsage() !== '')
585
            ) {
586
                $command = $command_tmp;
587
            }
588
        } elseif ($update_type !== null) {
589
            $command = $this->getCommandFromType($update_type);
590
        }
591
592
        //Make sure we don't try to process update that was already processed
593
        $last_id = DB::selectTelegramUpdate(1, $this->update->getUpdateId());
594
        if ($last_id && count($last_id) === 1) {
595
            TelegramLog::debug('Duplicate update received, processing aborted!');
596
            return Request::emptyResponse();
597
        }
598
599
        DB::insertRequest($this->update);
600
601
        return $this->executeCommand($command);
602
    }
603
604
    /**
605
     * Execute /command
606
     *
607
     * @param string $command
608
     *
609
     * @return ServerResponse
610
     * @throws TelegramException
611
     */
612
    public function executeCommand(string $command): ServerResponse
613
    {
614
        $command = mb_strtolower($command);
615
616
        $command_obj = $this->commands_objects[$command] ?? $this->getCommandObject($command);
617
618
        if (!$command_obj || !$command_obj->isEnabled()) {
619
            //Failsafe in case the Generic command can't be found
620
            if ($command === self::GENERIC_COMMAND) {
621
                throw new TelegramException('Generic command missing!');
622
            }
623
624
            //Handle a generic command or non existing one
625
            $this->last_command_response = $this->executeCommand(self::GENERIC_COMMAND);
626
        } else {
627
            //execute() method is executed after preExecute()
628
            //This is to prevent executing a DB query without a valid connection
629
            $this->last_command_response = $command_obj->preExecute();
630
        }
631
632
        return $this->last_command_response;
633
    }
634
635
    /**
636
     * Sanitize Command
637
     *
638
     * @param string $command
639
     *
640
     * @return string
641
     */
642 1
    protected function sanitizeCommand(string $command): string
643
    {
644 1
        return str_replace(' ', '', $this->ucWordsUnicode(str_replace('_', ' ', $command)));
645
    }
646
647
    /**
648
     * Enable a single Admin account
649
     *
650
     * @param int $admin_id Single admin id
651
     *
652
     * @return Telegram
653
     */
654 1
    public function enableAdmin(int $admin_id): Telegram
655
    {
656 1
        if ($admin_id <= 0) {
657
            TelegramLog::error('Invalid value "' . $admin_id . '" for admin.');
658 1
        } elseif (!in_array($admin_id, $this->admins_list, true)) {
659 1
            $this->admins_list[] = $admin_id;
660
        }
661
662 1
        return $this;
663
    }
664
665
    /**
666
     * Enable a list of Admin Accounts
667
     *
668
     * @param array $admin_ids List of admin ids
669
     *
670
     * @return Telegram
671
     */
672 1
    public function enableAdmins(array $admin_ids): Telegram
673
    {
674 1
        foreach ($admin_ids as $admin_id) {
675 1
            $this->enableAdmin($admin_id);
676
        }
677
678 1
        return $this;
679
    }
680
681
    /**
682
     * Get list of admins
683
     *
684
     * @return array
685
     */
686 1
    public function getAdminList(): array
687
    {
688 1
        return $this->admins_list;
689
    }
690
691
    /**
692
     * Check if the passed user is an admin
693
     *
694
     * If no user id is passed, the current update is checked for a valid message sender.
695
     *
696
     * @param int|null $user_id
697
     *
698
     * @return bool
699
     */
700 1
    public function isAdmin($user_id = null): bool
701
    {
702 1
        if ($user_id === null && $this->update !== null) {
703
            //Try to figure out if the user is an admin
704
            $update_methods = [
705
                'getMessage',
706
                'getEditedMessage',
707
                'getChannelPost',
708
                'getEditedChannelPost',
709
                'getInlineQuery',
710
                'getChosenInlineResult',
711
                'getCallbackQuery',
712
            ];
713
            foreach ($update_methods as $update_method) {
714
                $object = call_user_func([$this->update, $update_method]);
715
                if ($object !== null && $from = $object->getFrom()) {
716
                    $user_id = $from->getId();
717
                    break;
718
                }
719
            }
720
        }
721
722 1
        return ($user_id === null) ? false : in_array($user_id, $this->admins_list, true);
723
    }
724
725
    /**
726
     * Check if user required the db connection
727
     *
728
     * @return bool
729
     */
730
    public function isDbEnabled(): bool
731
    {
732
        return $this->mysql_enabled;
733
    }
734
735
    /**
736
     * Add a single custom commands path
737
     *
738
     * @param string $path   Custom commands path to add
739
     * @param bool   $before If the path should be prepended or appended to the list
740
     *
741
     * @return Telegram
742
     */
743 31
    public function addCommandsPath(string $path, bool $before = true): Telegram
744
    {
745 31
        if (!is_dir($path)) {
746 1
            TelegramLog::error('Commands path "' . $path . '" does not exist.');
747 31
        } elseif (!in_array($path, $this->commands_paths, true)) {
748 31
            if ($before) {
749 31
                array_unshift($this->commands_paths, $path);
750
            } else {
751
                $this->commands_paths[] = $path;
752
            }
753
        }
754
755 31
        return $this;
756
    }
757
758
    /**
759
     * Add multiple custom commands paths
760
     *
761
     * @param array $paths  Custom commands paths to add
762
     * @param bool  $before If the paths should be prepended or appended to the list
763
     *
764
     * @return Telegram
765
     */
766 1
    public function addCommandsPaths(array $paths, $before = true): Telegram
767
    {
768 1
        foreach ($paths as $path) {
769 1
            $this->addCommandsPath($path, $before);
770
        }
771
772 1
        return $this;
773
    }
774
775
    /**
776
     * Return the list of commands paths
777
     *
778
     * @return array
779
     */
780 1
    public function getCommandsPaths(): array
781
    {
782 1
        return $this->commands_paths;
783
    }
784
785
    /**
786
     * Set custom upload path
787
     *
788
     * @param string $path Custom upload path
789
     *
790
     * @return Telegram
791
     */
792 1
    public function setUploadPath(string $path): Telegram
793
    {
794 1
        $this->upload_path = $path;
795
796 1
        return $this;
797
    }
798
799
    /**
800
     * Get custom upload path
801
     *
802
     * @return string
803
     */
804 1
    public function getUploadPath(): string
805
    {
806 1
        return $this->upload_path;
807
    }
808
809
    /**
810
     * Set custom download path
811
     *
812
     * @param string $path Custom download path
813
     *
814
     * @return Telegram
815
     */
816 1
    public function setDownloadPath(string $path): Telegram
817
    {
818 1
        $this->download_path = $path;
819
820 1
        return $this;
821
    }
822
823
    /**
824
     * Get custom download path
825
     *
826
     * @return string
827
     */
828 1
    public function getDownloadPath(): string
829
    {
830 1
        return $this->download_path;
831
    }
832
833
    /**
834
     * Set command config
835
     *
836
     * Provide further variables to a particular commands.
837
     * For example you can add the channel name at the command /sendtochannel
838
     * Or you can add the api key for external service.
839
     *
840
     * @param string $command
841
     * @param array  $config
842
     *
843
     * @return Telegram
844
     */
845 13
    public function setCommandConfig(string $command, array $config): Telegram
846
    {
847 13
        $this->commands_config[$command] = $config;
848
849 13
        return $this;
850
    }
851
852
    /**
853
     * Get command config
854
     *
855
     * @param string $command
856
     *
857
     * @return array
858
     */
859 14
    public function getCommandConfig(string $command): array
860
    {
861 14
        return $this->commands_config[$command] ?? [];
862
    }
863
864
    /**
865
     * Get API key
866
     *
867
     * @return string
868
     */
869 1
    public function getApiKey(): string
870
    {
871 1
        return $this->api_key;
872
    }
873
874
    /**
875
     * Get Bot name
876
     *
877
     * @return string
878
     */
879 2
    public function getBotUsername(): string
880
    {
881 2
        return $this->bot_username;
882
    }
883
884
    /**
885
     * Get Bot Id
886
     *
887
     * @return int
888
     */
889
    public function getBotId(): int
890
    {
891
        return $this->bot_id;
892
    }
893
894
    /**
895
     * Get Version
896
     *
897
     * @return string
898
     */
899
    public function getVersion(): string
900
    {
901
        return $this->version;
902
    }
903
904
    /**
905
     * Set Webhook for bot
906
     *
907
     * @param string $url
908
     * @param array  $data Optional parameters.
909
     *
910
     * @return ServerResponse
911
     * @throws TelegramException
912
     */
913
    public function setWebhook(string $url, array $data = []): ServerResponse
914
    {
915
        if ($url === '') {
916
            throw new TelegramException('Hook url is empty!');
917
        }
918
919
        $data        = array_intersect_key($data, array_flip([
920
            'certificate',
921
            'max_connections',
922
            'allowed_updates',
923
        ]));
924
        $data['url'] = $url;
925
926
        // If the certificate is passed as a path, encode and add the file to the data array.
927
        if (!empty($data['certificate']) && is_string($data['certificate'])) {
928
            $data['certificate'] = Request::encodeFile($data['certificate']);
929
        }
930
931
        $result = Request::setWebhook($data);
932
933
        if (!$result->isOk()) {
934
            throw new TelegramException(
935
                'Webhook was not set! Error: ' . $result->getErrorCode() . ' ' . $result->getDescription()
936
            );
937
        }
938
939
        return $result;
940
    }
941
942
    /**
943
     * Delete any assigned webhook
944
     *
945
     * @param array $data
946
     *
947
     * @return ServerResponse
948
     * @throws TelegramException
949
     */
950
    public function deleteWebhook(array $data = []): ServerResponse
951
    {
952
        $result = Request::deleteWebhook($data);
953
954
        if (!$result->isOk()) {
955
            throw new TelegramException(
956
                'Webhook was not deleted! Error: ' . $result->getErrorCode() . ' ' . $result->getDescription()
957
            );
958
        }
959
960
        return $result;
961
    }
962
963
    /**
964
     * Replace function `ucwords` for UTF-8 characters in the class definition and commands
965
     *
966
     * @param string $str
967
     * @param string $encoding (default = 'UTF-8')
968
     *
969
     * @return string
970
     */
971 1
    protected function ucWordsUnicode(string $str, string $encoding = 'UTF-8'): string
972
    {
973 1
        return mb_convert_case($str, MB_CASE_TITLE, $encoding);
974
    }
975
976
    /**
977
     * Replace function `ucfirst` for UTF-8 characters in the class definition and commands
978
     *
979
     * @param string $str
980
     * @param string $encoding (default = 'UTF-8')
981
     *
982
     * @return string
983
     */
984 1
    protected function ucFirstUnicode(string $str, string $encoding = 'UTF-8'): string
985
    {
986 1
        return mb_strtoupper(mb_substr($str, 0, 1, $encoding), $encoding)
987 1
            . mb_strtolower(mb_substr($str, 1, mb_strlen($str), $encoding), $encoding);
988
    }
989
990
    /**
991
     * Enable requests limiter
992
     *
993
     * @param array $options
994
     *
995
     * @return Telegram
996
     * @throws TelegramException
997
     */
998
    public function enableLimiter(array $options = []): Telegram
999
    {
1000
        Request::setLimiter(true, $options);
1001
1002
        return $this;
1003
    }
1004
1005
    /**
1006
     * Run provided commands
1007
     *
1008
     * @param array $commands
1009
     *
1010
     * @throws TelegramException
1011
     */
1012
    public function runCommands(array $commands): void
1013
    {
1014
        if (empty($commands)) {
1015
            throw new TelegramException('No command(s) provided!');
1016
        }
1017
1018
        $this->run_commands = true;
1019
1020
        $result = Request::getMe();
1021
1022
        if ($result->isOk()) {
1023
            $result = $result->getResult();
1024
1025
            $bot_id       = $result->getId();
1026
            $bot_name     = $result->getFirstName();
1027
            $bot_username = $result->getUsername();
1028
        } else {
1029
            $bot_id       = $this->getBotId();
1030
            $bot_name     = $this->getBotUsername();
1031
            $bot_username = $this->getBotUsername();
1032
        }
1033
1034
        // Give bot access to admin commands
1035
        $this->enableAdmin($bot_id);
1036
1037
        $newUpdate = static function ($text = '') use ($bot_id, $bot_name, $bot_username) {
1038
            return new Update([
1039
                'update_id' => 0,
1040
                'message'   => [
1041
                    'message_id' => 0,
1042
                    'from'       => [
1043
                        'id'         => $bot_id,
1044
                        'first_name' => $bot_name,
1045
                        'username'   => $bot_username,
1046
                    ],
1047
                    'date'       => time(),
1048
                    'chat'       => [
1049
                        'id'   => $bot_id,
1050
                        'type' => 'private',
1051
                    ],
1052
                    'text'       => $text,
1053
                ],
1054
            ]);
1055
        };
1056
1057
        foreach ($commands as $command) {
1058
            $this->update = $newUpdate($command);
1059
1060
            // Load up-to-date commands list
1061
            if (empty($this->commands_objects)) {
1062
                $this->commands_objects = $this->getCommandsList();
1063
            }
1064
1065
            $this->executeCommand($this->update->getMessage()->getCommand());
1066
        }
1067
    }
1068
1069
    /**
1070
     * Is this session initiated by runCommands()
1071
     *
1072
     * @return bool
1073
     */
1074
    public function isRunCommands(): bool
1075
    {
1076
        return $this->run_commands;
1077
    }
1078
1079
    /**
1080
     * Switch to enable running getUpdates without a database
1081
     *
1082
     * @param bool $enable
1083
     *
1084
     * @return Telegram
1085
     */
1086
    public function useGetUpdatesWithoutDatabase(bool $enable = true): Telegram
1087
    {
1088
        $this->getupdates_without_database = $enable;
1089
1090
        return $this;
1091
    }
1092
1093
    /**
1094
     * Return last update id
1095
     *
1096
     * @return int
1097
     */
1098
    public function getLastUpdateId(): int
1099
    {
1100
        return $this->last_update_id;
1101
    }
1102
1103
    /**
1104
     * Set an update filter callback
1105
     *
1106
     * @param callable $callback
1107
     *
1108
     * @return Telegram
1109
     */
1110 1
    public function setUpdateFilter(callable $callback): Telegram
1111
    {
1112 1
        $this->update_filter = $callback;
1113
1114 1
        return $this;
1115
    }
1116
1117
    /**
1118
     * Return update filter callback
1119
     *
1120
     * @return callable|null
1121
     */
1122
    public function getUpdateFilter(): ?callable
1123
    {
1124
        return $this->update_filter;
1125
    }
1126
}
1127