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

Telegram::getCommandObject()   C

Complexity

Conditions 12
Paths 35

Size

Total Lines 44
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 24.6381

Importance

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

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

279
                throw new TelegramException('Error getting commands from path: ' . $path, /** @scrutinizer ignore-type */ $e);
Loading history...
280
            }
281
        }
282
283 1
        return $commands;
284
    }
285
286
    /**
287
     * Get an object instance of the passed command
288
     *
289
     * @param string $command
290
     * @param string $filepath
291
     *
292
     * @return Command|null
293
     */
294 1
    public function getCommandObject(string $command, string $filepath = ''): ?Command
295
    {
296 1
        if (isset($this->commands_objects[$command])) {
297
            return $this->commands_objects[$command];
298
        }
299
300 1
        $which = ['System'];
301 1
        $this->isAdmin() && $which[] = 'Admin';
302 1
        $which[] = 'User';
303
304 1
        foreach ($which as $auth) {
305 1
            if ($filepath) {
306 1
                $command_namespace = $this->getFileNamespace($filepath);
307
            } else {
308
                $command_namespace = __NAMESPACE__ . '\\Commands\\' . $auth . 'Commands';
309
            }
310 1
            $command_class = $command_namespace . '\\' . $this->ucFirstUnicode($command) . 'Command';
311
312 1
            if (class_exists($command_class)) {
313 1
                $command_obj = new $command_class($this, $this->update);
314
315 1
                switch ($auth) {
316 1
                    case 'System':
317 1
                        if ($command_obj instanceof SystemCommand) {
318 1
                            return $command_obj;
319
                        }
320
                        break;
321
322
                    case 'Admin':
323
                        if ($command_obj instanceof AdminCommand) {
324
                            return $command_obj;
325
                        }
326
                        break;
327
328
                    case 'User':
329
                        if ($command_obj instanceof UserCommand) {
330
                            return $command_obj;
331
                        }
332
                        break;
333
                }
334
            }
335
        }
336
337
        return null;
338
    }
339
340
    /**
341
     * Get namespace from php file by src path
342
     *
343
     * @param string $src (absolute path to file)
344
     *
345
     * @return string|null ("Longman\TelegramBot\Commands\SystemCommands" for example)
346
     */
347 1
    protected function getFileNamespace(string $src): ?string
348
    {
349 1
        $content = file_get_contents($src);
350 1
        if (preg_match('#^namespace\s+(.+?);#m', $content, $m)) {
351 1
            return $m[1];
352
        }
353
354
        return null;
355
    }
356
357
    /**
358
     * Set custom input string for debug purposes
359
     *
360
     * @param string $input (json format)
361
     *
362
     * @return Telegram
363
     */
364
    public function setCustomInput(string $input): Telegram
365
    {
366
        $this->input = $input;
367
368
        return $this;
369
    }
370
371
    /**
372
     * Get custom input string for debug purposes
373
     *
374
     * @return string
375
     */
376
    public function getCustomInput(): string
377
    {
378
        return $this->input;
379
    }
380
381
    /**
382
     * Get the ServerResponse of the last Command execution
383
     *
384
     * @return ServerResponse
385
     */
386
    public function getLastCommandResponse(): ServerResponse
387
    {
388
        return $this->last_command_response;
389
    }
390
391
    /**
392
     * Handle getUpdates method
393
     *
394
     * @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