Passed
Push — master ( 7ecda6...2093cf )
by Armando
04:22 queued 02:31
created

Telegram::enableAdmins()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
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.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