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