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

Telegram::handleGetUpdates()   C

Complexity

Conditions 17
Paths 38

Size

Total Lines 82
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 306

Importance

Changes 11
Bugs 0 Features 1
Metric Value
cc 17
eloc 44
c 11
b 0
f 1
nc 38
nop 2
dl 0
loc 82
ccs 0
cts 41
cp 0
crap 306
rs 5.2166

How to fix   Long Method    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