Passed
Push — master ( 2093cf...9ca615 )
by Armando
04:04 queued 55s
created

Telegram::getCommandConfig()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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