Completed
Push — 772-prevent_system_command_cal... ( bf27ea )
by Armando
02:31
created

Telegram::getCommandObject()   C

Complexity

Conditions 7
Paths 8

Size

Total Lines 24
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 0
cts 11
cp 0
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 11
nc 8
nop 1
crap 56
1
<?php
2
/**
3
 * This file is part of the TelegramBot package.
4
 *
5
 * (c) Avtandil Kikabidze aka LONGMAN <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Longman\TelegramBot;
12
13 1
define('BASE_PATH', __DIR__);
14 1
define('BASE_COMMANDS_PATH', BASE_PATH . '/Commands');
15
16
use Exception;
17
use Longman\TelegramBot\Commands\Command;
18
use Longman\TelegramBot\Entities\ServerResponse;
19
use Longman\TelegramBot\Entities\Update;
20
use Longman\TelegramBot\Exception\TelegramException;
21
use PDO;
22
use RecursiveDirectoryIterator;
23
use RecursiveIteratorIterator;
24
use RegexIterator;
25
26
class Telegram
27
{
28
    /**
29
     * Version
30
     *
31
     * @var string
32
     */
33
    protected $version = '0.52.0';
34
35
    /**
36
     * Telegram API key
37
     *
38
     * @var string
39
     */
40
    protected $api_key = '';
41
42
    /**
43
     * Telegram Bot username
44
     *
45
     * @var string
46
     */
47
    protected $bot_username = '';
48
49
    /**
50
     * Telegram Bot id
51
     *
52
     * @var string
53
     */
54
    protected $bot_id = '';
55
56
    /**
57
     * Raw request data (json) for webhook methods
58
     *
59
     * @var string
60
     */
61
    protected $input;
62
63
    /**
64
     * Custom commands paths
65
     *
66
     * @var array
67
     */
68
    protected $commands_paths = [];
69
70
    /**
71
     * Current Update object
72
     *
73
     * @var \Longman\TelegramBot\Entities\Update
74
     */
75
    protected $update;
76
77
    /**
78
     * Upload path
79
     *
80
     * @var string
81
     */
82
    protected $upload_path;
83
84
    /**
85
     * Download path
86
     *
87
     * @var string
88
     */
89
    protected $download_path;
90
91
    /**
92
     * MySQL integration
93
     *
94
     * @var boolean
95
     */
96
    protected $mysql_enabled = false;
97
98
    /**
99
     * PDO object
100
     *
101
     * @var \PDO
102
     */
103
    protected $pdo;
104
105
    /**
106
     * Commands config
107
     *
108
     * @var array
109
     */
110
    protected $commands_config = [];
111
112
    /**
113
     * Admins list
114
     *
115
     * @var array
116
     */
117
    protected $admins_list = [];
118
119
    /**
120
     * ServerResponse of the last Command execution
121
     *
122
     * @var \Longman\TelegramBot\Entities\ServerResponse
123
     */
124
    protected $last_command_response;
125
126
    /**
127
     * Botan.io integration
128
     *
129
     * @var boolean
130
     */
131
    protected $botan_enabled = false;
132
133
    /**
134
     * Check if runCommands() is running in this session
135
     *
136
     * @var boolean
137
     */
138
    protected $run_commands = false;
139
140
    /**
141
     * If the current message is a user-called command (as opposed to system-called)
142
     *
143
     * @var boolean
144
     */
145
    protected $is_user_command = false;
146
147
    /**
148
     * Telegram constructor.
149
     *
150
     * @param string $api_key
151
     * @param string $bot_username
152
     *
153
     * @throws \Longman\TelegramBot\Exception\TelegramException
154
     */
155 30
    public function __construct($api_key, $bot_username = '')
156
    {
157 30
        if (empty($api_key)) {
158 1
            throw new TelegramException('API KEY not defined!');
159
        }
160 30
        preg_match('/(\d+)\:[\w\-]+/', $api_key, $matches);
161 30
        if (!isset($matches[1])) {
162 1
            throw new TelegramException('Invalid API KEY defined!');
163
        }
164 30
        $this->bot_id  = $matches[1];
165 30
        $this->api_key = $api_key;
166
167 30
        if (!empty($bot_username)) {
168 30
            $this->bot_username = $bot_username;
169
        }
170
171 30
        Request::initialize($this);
172 30
    }
173
174
    /**
175
     * Initialize Database connection
176
     *
177
     * @param array  $credential
178
     * @param string $table_prefix
179
     * @param string $encoding
180
     *
181
     * @return \Longman\TelegramBot\Telegram
182
     * @throws \Longman\TelegramBot\Exception\TelegramException
183
     */
184 9 View Code Duplication
    public function enableMySql(array $credential, $table_prefix = null, $encoding = 'utf8mb4')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
185
    {
186 9
        $this->pdo = DB::initialize($credential, $this, $table_prefix, $encoding);
187 9
        ConversationDB::initializeConversation();
188 9
        $this->mysql_enabled = true;
189
190 9
        return $this;
191
    }
192
193
    /**
194
     * Initialize Database external connection
195
     *
196
     * @param PDO    $external_pdo_connection PDO database object
197
     * @param string $table_prefix
198
     *
199
     * @return \Longman\TelegramBot\Telegram
200
     * @throws \Longman\TelegramBot\Exception\TelegramException
201
     */
202 View Code Duplication
    public function enableExternalMySql($external_pdo_connection, $table_prefix = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
    {
204
        $this->pdo = DB::externalInitialize($external_pdo_connection, $this, $table_prefix);
205
        ConversationDB::initializeConversation();
206
        $this->mysql_enabled = true;
207
208
        return $this;
209
    }
210
211
    /**
212
     * Get commands list
213
     *
214
     * @return array $commands
215
     * @throws \Longman\TelegramBot\Exception\TelegramException
216
     */
217 1
    public function getCommandsList()
218
    {
219 1
        $commands = [];
220
221 1
        foreach ($this->commands_paths as $path) {
222
            try {
223
                //Get all "*Command.php" files
224
                $files = new RegexIterator(
225
                    new RecursiveIteratorIterator(
226
                        new RecursiveDirectoryIterator($path)
227
                    ),
228
                    '/^.+Command.php$/'
229
                );
230
231
                foreach ($files as $file) {
232
                    //Remove "Command.php" from filename
233
                    $command      = $this->sanitizeCommand(substr($file->getFilename(), 0, -11));
234
                    $command_name = strtolower($command);
235
236
                    if (array_key_exists($command_name, $commands)) {
237
                        continue;
238
                    }
239
240
                    require_once $file->getPathname();
241
242
                    $command_obj = $this->getCommandObject($command_name);
243
                    if ($command_obj instanceof Command) {
244
                        $commands[$command_name] = $command_obj;
245
                    }
246
                }
247
            } catch (Exception $e) {
248
                throw new TelegramException('Error getting commands from path: ' . $path);
249
            }
250
        }
251
252 1
        return $commands;
253
    }
254
255
    /**
256
     * Get an object instance of the passed command
257
     *
258
     * @param string $command
259
     *
260
     * @return \Longman\TelegramBot\Commands\Command|null
261
     */
262
    public function getCommandObject($command)
263
    {
264
        $which = ['System'];
265
        $this->isAdmin() && $which[] = 'Admin';
266
        $which[] = 'User';
267
268
        foreach ($which as $auth) {
269
            // Allow only "generic" system commands if a user calls a system command, to allow (custom) fallbacks.
270
            // The problem here is, that the system command gets autoloaded already, so even if it's not in the
271
            // custom commands paths, it is still available and there is no way of knowing if it's an override or not.
272
            // Downside: `/generic` and `/genericmessage` can still be called by user.
273
            // @todo Implement proper command system...
274
            if ($auth === 'System' && $this->is_user_command && !in_array($command, ['genericmessage', 'generic'], true)) {
275
                continue;
276
            }
277
278
            $command_namespace = __NAMESPACE__ . '\\Commands\\' . $auth . 'Commands\\' . $this->ucfirstUnicode($command) . 'Command';
279
            if (class_exists($command_namespace)) {
280
                return new $command_namespace($this, $this->update);
281
            }
282
        }
283
284
        return null;
285
    }
286
287
    /**
288
     * Set custom input string for debug purposes
289
     *
290
     * @param string $input (json format)
291
     *
292
     * @return \Longman\TelegramBot\Telegram
293
     */
294
    public function setCustomInput($input)
295
    {
296
        $this->input = $input;
297
298
        return $this;
299
    }
300
301
    /**
302
     * Get custom input string for debug purposes
303
     *
304
     * @return string
305
     */
306
    public function getCustomInput()
307
    {
308
        return $this->input;
309
    }
310
311
    /**
312
     * Get the ServerResponse of the last Command execution
313
     *
314
     * @return \Longman\TelegramBot\Entities\ServerResponse
315
     */
316
    public function getLastCommandResponse()
317
    {
318
        return $this->last_command_response;
319
    }
320
321
    /**
322
     * Handle getUpdates method
323
     *
324
     * @param int|null $limit
325
     * @param int|null $timeout
326
     *
327
     * @return \Longman\TelegramBot\Entities\ServerResponse
328
     * @throws \Longman\TelegramBot\Exception\TelegramException
329
     */
330
    public function handleGetUpdates($limit = null, $timeout = null)
331
    {
332
        if (empty($this->bot_username)) {
333
            throw new TelegramException('Bot Username is not defined!');
334
        }
335
336
        if (!DB::isDbConnected()) {
337
            return new ServerResponse(
338
                [
339
                    'ok'          => false,
340
                    'description' => 'getUpdates needs MySQL connection!',
341
                ],
342
                $this->bot_username
343
            );
344
        }
345
346
        //Take custom input into account.
347
        if ($custom_input = $this->getCustomInput()) {
348
            $response = new ServerResponse(json_decode($custom_input, true), $this->bot_username);
349
        } else {
350
            //DB Query
351
            $last_update = DB::selectTelegramUpdate(1);
352
            $last_update = reset($last_update);
353
354
            //As explained in the telegram bot api documentation
355
            $offset = isset($last_update['id']) ? $last_update['id'] + 1 : null;
356
357
            $response = Request::getUpdates(
358
                [
359
                    'offset'  => $offset,
360
                    'limit'   => $limit,
361
                    'timeout' => $timeout,
362
                ]
363
            );
364
        }
365
366
        if ($response->isOk()) {
367
            //Process all updates
368
            /** @var Update $result */
369
            foreach ((array) $response->getResult() as $result) {
370
                $this->processUpdate($result);
371
            }
372
        }
373
374
        return $response;
375
    }
376
377
    /**
378
     * Handle bot request from webhook
379
     *
380
     * @return bool
381
     *
382
     * @throws \Longman\TelegramBot\Exception\TelegramException
383
     */
384
    public function handle()
385
    {
386
        if (empty($this->bot_username)) {
387
            throw new TelegramException('Bot Username is not defined!');
388
        }
389
390
        $this->input = Request::getInput();
391
392
        if (empty($this->input)) {
393
            throw new TelegramException('Input is empty!');
394
        }
395
396
        $post = json_decode($this->input, true);
397
        if (empty($post)) {
398
            throw new TelegramException('Invalid JSON!');
399
        }
400
401
        if ($response = $this->processUpdate(new Update($post, $this->bot_username))) {
402
            return $response->isOk();
403
        }
404
405
        return false;
406
    }
407
408
    /**
409
     * Get the command name from the command type
410
     *
411
     * @param string $type
412
     *
413
     * @return string
414
     */
415
    protected function getCommandFromType($type)
416
    {
417
        return $this->ucfirstUnicode(str_replace('_', '', $type));
418
    }
419
420
    /**
421
     * Process bot Update request
422
     *
423
     * @param \Longman\TelegramBot\Entities\Update $update
424
     *
425
     * @return \Longman\TelegramBot\Entities\ServerResponse
426
     * @throws \Longman\TelegramBot\Exception\TelegramException
427
     */
428
    public function processUpdate(Update $update)
429
    {
430
        $this->update = $update;
431
432
        //If all else fails, it's a generic message.
433
        $command = 'genericmessage';
434
435
        $this->is_user_command = false;
436
437
        $update_type = $this->update->getUpdateType();
438
        if ($update_type === 'message') {
439
            $message = $this->update->getMessage();
440
441
            $type = $message->getType();
442
            if ($type === 'command') {
443
                $this->is_user_command = true;
444
                $command               = $message->getCommand();
445
            } elseif (in_array($type, [
446
                'new_chat_members',
447
                'left_chat_member',
448
                'new_chat_title',
449
                'new_chat_photo',
450
                'delete_chat_photo',
451
                'group_chat_created',
452
                'supergroup_chat_created',
453
                'channel_chat_created',
454
                'migrate_to_chat_id',
455
                'migrate_from_chat_id',
456
                'pinned_message',
457
                'invoice',
458
                'successful_payment',
459
            ], true)
460
            ) {
461
                $command = $this->getCommandFromType($type);
462
            }
463
        } else {
464
            $command = $this->getCommandFromType($update_type);
465
        }
466
467
        //Load admin commands
468
        if ($update_type === 'message' && $this->isAdmin()) {
469
            $this->addCommandsPath(BASE_COMMANDS_PATH . '/AdminCommands', false);
470
        }
471
        //Only load System Commands if the command hasn't been called by a user
472
        if (!$this->is_user_command) {
473
            $this->addCommandsPath(BASE_COMMANDS_PATH . '/SystemCommands', false);
474
        }
475
476
        //Make sure we have an up-to-date command list
477
        //This is necessary to "require" all the necessary command files!
478
        $this->getCommandsList();
479
480
        //Make sure we don't try to process update that was already processed
481
        $last_id = DB::selectTelegramUpdate(1, $this->update->getUpdateId());
482
        if ($last_id && count($last_id) === 1) {
483
            TelegramLog::debug('Duplicate update received, processing aborted!');
484
            return Request::emptyResponse();
485
        }
486
487
        DB::insertRequest($this->update);
488
489
        return $this->executeCommand($command);
490
    }
491
492
    /**
493
     * Execute /command
494
     *
495
     * @param string $command
496
     *
497
     * @return mixed
498
     * @throws \Longman\TelegramBot\Exception\TelegramException
499
     */
500
    public function executeCommand($command)
501
    {
502
        $command     = strtolower($command);
503
        $command_obj = $this->getCommandObject($command);
504
505
        if (!$command_obj || !$command_obj->isEnabled()) {
506
            //Failsafe in case the Generic command can't be found
507
            if ($command === 'generic') {
508
                throw new TelegramException('Generic command missing!');
509
            }
510
511
            //Handle a generic command or non existing one
512
            $this->last_command_response = $this->executeCommand('generic');
513
        } else {
514
            //Botan.io integration, make sure only the actual command user executed is reported
515
            if ($this->botan_enabled) {
516
                Botan::lock($command);
517
            }
518
519
            //execute() method is executed after preExecute()
520
            //This is to prevent executing a DB query without a valid connection
521
            $this->last_command_response = $command_obj->preExecute();
522
523
            //Botan.io integration, send report after executing the command
524
            if ($this->botan_enabled) {
525
                Botan::track($this->update, $command);
526
            }
527
        }
528
529
        return $this->last_command_response;
530
    }
531
532
    /**
533
     * Sanitize Command
534
     *
535
     * @param string $command
536
     *
537
     * @return string
538
     */
539
    protected function sanitizeCommand($command)
540
    {
541
        return str_replace(' ', '', $this->ucwordsUnicode(str_replace('_', ' ', $command)));
542
    }
543
544
    /**
545
     * Enable a single Admin account
546
     *
547
     * @param integer $admin_id Single admin id
548
     *
549
     * @return \Longman\TelegramBot\Telegram
550
     */
551 1
    public function enableAdmin($admin_id)
552
    {
553 1
        if (!is_int($admin_id) || $admin_id <= 0) {
554 1
            TelegramLog::error('Invalid value "%s" for admin.', $admin_id);
555 1
        } elseif (!in_array($admin_id, $this->admins_list, true)) {
556 1
            $this->admins_list[] = $admin_id;
557
        }
558
559 1
        return $this;
560
    }
561
562
    /**
563
     * Enable a list of Admin Accounts
564
     *
565
     * @param array $admin_ids List of admin ids
566
     *
567
     * @return \Longman\TelegramBot\Telegram
568
     */
569 1
    public function enableAdmins(array $admin_ids)
570
    {
571 1
        foreach ($admin_ids as $admin_id) {
572 1
            $this->enableAdmin($admin_id);
573
        }
574
575 1
        return $this;
576
    }
577
578
    /**
579
     * Get list of admins
580
     *
581
     * @return array
582
     */
583 1
    public function getAdminList()
584
    {
585 1
        return $this->admins_list;
586
    }
587
588
    /**
589
     * Check if the passed user is an admin
590
     *
591
     * If no user id is passed, the current update is checked for a valid message sender.
592
     *
593
     * @param int|null $user_id
594
     *
595
     * @return bool
596
     */
597
    public function isAdmin($user_id = null)
598
    {
599
        if ($user_id === null && $this->update !== null) {
600
            //Try to figure out if the user is an admin
601
            $update_methods = [
602
                'getMessage',
603
                'getEditedMessage',
604
                'getChannelPost',
605
                'getEditedChannelPost',
606
                'getInlineQuery',
607
                'getChosenInlineResult',
608
                'getCallbackQuery',
609
            ];
610
            foreach ($update_methods as $update_method) {
611
                $object = call_user_func([$this->update, $update_method]);
612
                if ($object !== null && $from = $object->getFrom()) {
613
                    $user_id = $from->getId();
614
                    break;
615
                }
616
            }
617
        }
618
619
        return ($user_id === null) ? false : in_array($user_id, $this->admins_list, true);
620
    }
621
622
    /**
623
     * Check if user required the db connection
624
     *
625
     * @return bool
626
     */
627
    public function isDbEnabled()
628
    {
629
        if ($this->mysql_enabled) {
630
            return true;
631
        } else {
632
            return false;
633
        }
634
    }
635
636
    /**
637
     * Add a single custom commands path
638
     *
639
     * @param string $path   Custom commands path to add
640
     * @param bool   $before If the path should be prepended or appended to the list
641
     *
642
     * @return \Longman\TelegramBot\Telegram
643
     */
644 1
    public function addCommandsPath($path, $before = true)
645
    {
646 1
        if (!is_dir($path)) {
647 1
            TelegramLog::error('Commands path "%s" does not exist.', $path);
648 1
        } elseif (!in_array($path, $this->commands_paths, true)) {
649 1
            if ($before) {
650 1
                array_unshift($this->commands_paths, $path);
651
            } else {
652
                $this->commands_paths[] = $path;
653
            }
654
        }
655
656 1
        return $this;
657
    }
658
659
    /**
660
     * Add multiple custom commands paths
661
     *
662
     * @param array $paths  Custom commands paths to add
663
     * @param bool  $before If the paths should be prepended or appended to the list
664
     *
665
     * @return \Longman\TelegramBot\Telegram
666
     */
667 1
    public function addCommandsPaths(array $paths, $before = true)
668
    {
669 1
        foreach ($paths as $path) {
670 1
            $this->addCommandsPath($path, $before);
671
        }
672
673 1
        return $this;
674
    }
675
676
    /**
677
     * Return the list of commands paths
678
     *
679
     * @return array
680
     */
681 1
    public function getCommandsPaths()
682
    {
683 1
        return $this->commands_paths;
684
    }
685
686
    /**
687
     * Set custom upload path
688
     *
689
     * @param string $path Custom upload path
690
     *
691
     * @return \Longman\TelegramBot\Telegram
692
     */
693
    public function setUploadPath($path)
694
    {
695
        $this->upload_path = $path;
696
697
        return $this;
698
    }
699
700
    /**
701
     * Get custom upload path
702
     *
703
     * @return string
704
     */
705
    public function getUploadPath()
706
    {
707
        return $this->upload_path;
708
    }
709
710
    /**
711
     * Set custom download path
712
     *
713
     * @param string $path Custom download path
714
     *
715
     * @return \Longman\TelegramBot\Telegram
716
     */
717
    public function setDownloadPath($path)
718
    {
719
        $this->download_path = $path;
720
721
        return $this;
722
    }
723
724
    /**
725
     * Get custom download path
726
     *
727
     * @return string
728
     */
729
    public function getDownloadPath()
730
    {
731
        return $this->download_path;
732
    }
733
734
    /**
735
     * Set command config
736
     *
737
     * Provide further variables to a particular commands.
738
     * For example you can add the channel name at the command /sendtochannel
739
     * Or you can add the api key for external service.
740
     *
741
     * @param string $command
742
     * @param array  $config
743
     *
744
     * @return \Longman\TelegramBot\Telegram
745
     */
746 14
    public function setCommandConfig($command, array $config)
747
    {
748 14
        $this->commands_config[$command] = $config;
749
750 14
        return $this;
751
    }
752
753
    /**
754
     * Get command config
755
     *
756
     * @param string $command
757
     *
758
     * @return array
759
     */
760 14
    public function getCommandConfig($command)
761
    {
762 14
        return isset($this->commands_config[$command]) ? $this->commands_config[$command] : [];
763
    }
764
765
    /**
766
     * Get API key
767
     *
768
     * @return string
769
     */
770 1
    public function getApiKey()
771
    {
772 1
        return $this->api_key;
773
    }
774
775
    /**
776
     * Get Bot name
777
     *
778
     * @return string
779
     */
780 1
    public function getBotUsername()
781
    {
782 1
        return $this->bot_username;
783
    }
784
785
    /**
786
     * Get Bot Id
787
     *
788
     * @return string
789
     */
790
    public function getBotId()
791
    {
792
        return $this->bot_id;
793
    }
794
795
    /**
796
     * Get Version
797
     *
798
     * @return string
799
     */
800
    public function getVersion()
801
    {
802
        return $this->version;
803
    }
804
805
    /**
806
     * Set Webhook for bot
807
     *
808
     * @param string $url
809
     * @param array  $data Optional parameters.
810
     *
811
     * @return \Longman\TelegramBot\Entities\ServerResponse
812
     * @throws \Longman\TelegramBot\Exception\TelegramException
813
     */
814
    public function setWebhook($url, array $data = [])
815
    {
816
        if (empty($url)) {
817
            throw new TelegramException('Hook url is empty!');
818
        }
819
820
        $data        = array_intersect_key($data, array_flip([
821
            'certificate',
822
            'max_connections',
823
            'allowed_updates',
824
        ]));
825
        $data['url'] = $url;
826
827
        // If the certificate is passed as a path, encode and add the file to the data array.
828
        if (!empty($data['certificate']) && is_string($data['certificate'])) {
829
            $data['certificate'] = Request::encodeFile($data['certificate']);
830
        }
831
832
        $result = Request::setWebhook($data);
833
834 View Code Duplication
        if (!$result->isOk()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
835
            throw new TelegramException(
836
                'Webhook was not set! Error: ' . $result->getErrorCode() . ' ' . $result->getDescription()
837
            );
838
        }
839
840
        return $result;
841
    }
842
843
    /**
844
     * Delete any assigned webhook
845
     *
846
     * @return mixed
847
     * @throws \Longman\TelegramBot\Exception\TelegramException
848
     */
849
    public function deleteWebhook()
850
    {
851
        $result = Request::deleteWebhook();
852
853 View Code Duplication
        if (!$result->isOk()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
854
            throw new TelegramException(
855
                'Webhook was not deleted! Error: ' . $result->getErrorCode() . ' ' . $result->getDescription()
856
            );
857
        }
858
859
        return $result;
860
    }
861
862
    /**
863
     * Replace function `ucwords` for UTF-8 characters in the class definition and commands
864
     *
865
     * @param string $str
866
     * @param string $encoding (default = 'UTF-8')
867
     *
868
     * @return string
869
     */
870
    protected function ucwordsUnicode($str, $encoding = 'UTF-8')
871
    {
872
        return mb_convert_case($str, MB_CASE_TITLE, $encoding);
873
    }
874
875
    /**
876
     * Replace function `ucfirst` for UTF-8 characters in the class definition and commands
877
     *
878
     * @param string $str
879
     * @param string $encoding (default = 'UTF-8')
880
     *
881
     * @return string
882
     */
883
    protected function ucfirstUnicode($str, $encoding = 'UTF-8')
884
    {
885
        return mb_strtoupper(mb_substr($str, 0, 1, $encoding), $encoding)
886
               . mb_strtolower(mb_substr($str, 1, mb_strlen($str), $encoding), $encoding);
887
    }
888
889
    /**
890
     * Enable Botan.io integration
891
     *
892
     * @param  string $token
893
     * @param  array  $options
894
     *
895
     * @return \Longman\TelegramBot\Telegram
896
     * @throws \Longman\TelegramBot\Exception\TelegramException
897
     */
898
    public function enableBotan($token, array $options = [])
899
    {
900
        Botan::initializeBotan($token, $options);
901
        $this->botan_enabled = true;
902
903
        return $this;
904
    }
905
906
    /**
907
     * Enable requests limiter
908
     *
909
     * @param  array $options
910
     *
911
     * @return \Longman\TelegramBot\Telegram
912
     */
913
    public function enableLimiter(array $options = [])
914
    {
915
        Request::setLimiter(true, $options);
916
917
        return $this;
918
    }
919
920
    /**
921
     * Run provided commands
922
     *
923
     * @param array $commands
924
     *
925
     * @throws TelegramException
926
     */
927
    public function runCommands($commands)
928
    {
929
        if (!is_array($commands) || empty($commands)) {
930
            throw new TelegramException('No command(s) provided!');
931
        }
932
933
        $this->run_commands  = true;
934
        $this->botan_enabled = false;   // Force disable Botan.io integration, we don't want to track self-executed commands!
935
936
        $result = Request::getMe();
937
938
        if ($result->isOk()) {
939
            $result = $result->getResult();
940
941
            $bot_id       = $result->getId();
942
            $bot_name     = $result->getFirstName();
943
            $bot_username = $result->getUsername();
944
        } else {
945
            $bot_id       = $this->getBotId();
946
            $bot_name     = $this->getBotUsername();
947
            $bot_username = $this->getBotUsername();
948
        }
949
950
951
        $this->enableAdmin($bot_id);    // Give bot access to admin commands
952
        $this->getCommandsList();       // Load full commands list
953
954
        foreach ($commands as $command) {
955
            $this->update = new Update(
956
                [
957
                    'update_id' => 0,
958
                    'message'   => [
959
                        'message_id' => 0,
960
                        'from'       => [
961
                            'id'         => $bot_id,
962
                            'first_name' => $bot_name,
963
                            'username'   => $bot_username,
964
                        ],
965
                        'date'       => time(),
966
                        'chat'       => [
967
                            'id'   => $bot_id,
968
                            'type' => 'private',
969
                        ],
970
                        'text'       => $command,
971
                    ],
972
                ]
973
            );
974
975
            $this->executeCommand($this->update->getMessage()->getCommand());
976
        }
977
    }
978
979
    /**
980
     * Is this session initiated by runCommands()
981
     *
982
     * @return bool
983
     */
984
    public function isRunCommands()
985
    {
986
        return $this->run_commands;
987
    }
988
}
989