Completed
Branch feature/improve-code (a8c279)
by Avtandil
06:09
created

Telegram::setCustomInput()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 5
rs 9.4285
c 1
b 0
f 0
ccs 0
cts 3
cp 0
cc 1
eloc 3
nc 1
nop 1
crap 2
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 Longman\TelegramBot\Entities\Update;
17
use Longman\TelegramBot\Exception\TelegramException;
18
19
class Telegram
20
{
21
    /**
22
     * Version
23
     *
24
     * @var string
25
     */
26
    protected $version = '0.35.0';
27
28
    /**
29
     * Telegram API key
30
     *
31
     * @var string
32
     */
33
    protected $api_key = '';
34
35
    /**
36
     * Telegram Bot name
37
     *
38
     * @var string
39
     */
40
    protected $bot_name = '';
41
42
    /**
43
     * Raw request data (json) for webhook methods
44
     *
45
     * @var string
46
     */
47
    protected $input;
48
49
    /**
50
     * Custom commands paths
51
     *
52
     * @var array
53
     */
54
    protected $commands_paths = [];
55
56
    /**
57
     * Current Update object
58
     *
59
     * @var \Longman\TelegramBot\Entities\Update
60
     */
61
    protected $update;
62
63
    /**
64
     * Upload path
65
     *
66
     * @var string
67
     */
68
    protected $upload_path;
69
70
    /**
71
     * Download path
72
     *
73
     * @var string
74
     */
75
    protected $download_path;
76
77
    /**
78
     * MySQL integration
79
     *
80
     * @var boolean
81
     */
82
    protected $mysql_enabled = false;
83
84
    /**
85
     * PDO object
86
     *
87
     * @var \PDO
88
     */
89
    protected $pdo;
90
91
    /**
92
     * Commands config
93
     *
94
     * @var array
95
     */
96
    protected $commands_config = [];
97
98
    /**
99
     * Admins list
100
     *
101
     * @var array
102
     */
103
    protected $admins_list = [];
104
105
    /**
106
     * ServerResponse of the last Command execution
107
     *
108
     * @var \Longman\TelegramBot\Entities\ServerResponse
109
     */
110
    protected $last_command_response;
111
112
    /**
113
     * Botan.io integration
114
     *
115
     * @var boolean
116
     */
117
    protected $botan_enabled = false;
118
119
    /**
120
     * Telegram constructor.
121
     *
122
     * @param $api_key
123
     * @param $bot_name
124
     * @throws \Longman\TelegramBot\Exception\TelegramException
125
     */
126 39
    public function __construct($api_key, $bot_name)
127
    {
128 39
        if (empty($api_key)) {
129 1
            throw new TelegramException('API KEY not defined!');
130
        }
131
132 39
        if (empty($bot_name)) {
133 1
            throw new TelegramException('Bot Username not defined!');
134
        }
135
136 39
        $this->api_key  = $api_key;
137 39
        $this->bot_name = $bot_name;
138
139
        //Set default download and upload path
140 39
        $this->setDownloadPath(BASE_PATH . '/../Download');
141 39
        $this->setUploadPath(BASE_PATH . '/../Upload');
142
143
        //Add default system commands path
144 39
        $this->addCommandsPath(BASE_COMMANDS_PATH . '/SystemCommands');
145
146 39
        Request::initialize($this);
147 39
    }
148
149
    /**
150
     * Initialize Database connection
151
     *
152
     * @param array  $credential
153
     * @param string $table_prefix
154
     * @param string $encoding
155
     *
156
     * @return \Longman\TelegramBot\Telegram
157
     */
158 9
    public function enableMySql(array $credential, $table_prefix = null, $encoding = 'utf8mb4')
159
    {
160 9
        $this->pdo = DB::initialize($credential, $this, $table_prefix, $encoding);
161 9
        ConversationDB::initializeConversation();
162 9
        $this->mysql_enabled = true;
163 9
        return $this;
164
    }
165
166
    /**
167
     * Initialize Database external connection
168
     *
169
     * @param /PDO    $external_pdo_connection PDO database object
0 ignored issues
show
Documentation introduced by
The doc-type /PDO could not be parsed: Unknown type name "/PDO" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
170
     * @param string $table_prefix
171
     */
172
    public function enableExternalMysql($external_pdo_connection, $table_prefix = null)
173
    {
174
        $this->pdo = DB::externalInitialize($external_pdo_connection, $this, $table_prefix);
175
        ConversationDB::initializeConversation();
176
        $this->mysql_enabled = true;
177
    }
178
179
    /**
180
     * Get commands list
181
     *
182
     * @return array $commands
183
     * @throws \Longman\TelegramBot\Exception\TelegramException
184
     */
185 10
    public function getCommandsList()
186
    {
187 10
        $commands = [];
188
189 10
        foreach ($this->commands_paths as $path) {
190
            try {
191
                //Get all "*Command.php" files
192 10
                $files = new \RegexIterator(
193 10
                    new \RecursiveIteratorIterator(
194 10
                        new \RecursiveDirectoryIterator($path)
195 10
                    ),
196
                    '/^.+Command.php$/'
197 10
                );
198
199 10
                foreach ($files as $file) {
200
                    //Remove "Command.php" from filename
201 10
                    $command      = $this->sanitizeCommand(substr($file->getFilename(), 0, -11));
202 10
                    $command_name = strtolower($command);
203
204 10
                    if (array_key_exists($command_name, $commands)) {
205
                        continue;
206
                    }
207
208 10
                    require_once $file->getPathname();
209
210 10
                    $command_obj = $this->getCommandObject($command);
211 10
                    if ($command_obj instanceof Commands\Command) {
212 10
                        $commands[$command_name] = $command_obj;
213 10
                    }
214 10
                }
215 10
            } catch (\Exception $e) {
216
                throw new TelegramException('Error getting commands from path: ' . $path);
217
            }
218 10
        }
219
220 10
        return $commands;
221
    }
222
223
    /**
224
     * Get an object instance of the passed command
225
     *
226
     * @param string $command
227
     *
228
     * @return \Longman\TelegramBot\Commands\Command|null
229
     */
230 11
    public function getCommandObject($command)
231
    {
232 11
        $which = ['System'];
233 11
        ($this->isAdmin()) && $which[] = 'Admin';
234 11
        $which[] = 'User';
235
236 11
        foreach ($which as $auth) {
237 11
            $command_namespace = __NAMESPACE__ . '\\Commands\\' . $auth . 'Commands\\' . $this->ucfirstUnicode($command) . 'Command';
238 11
            if (class_exists($command_namespace)) {
239 11
                return new $command_namespace($this, $this->update);
240
            }
241 10
        }
242
243
        return null;
244
    }
245
246
    /**
247
     * Set custom input string for debug purposes
248
     *
249
     * @param string $input (json format)
250
     *
251
     * @return \Longman\TelegramBot\Telegram
252
     */
253
    public function setCustomInput($input)
254
    {
255
        $this->input = $input;
256
        return $this;
257
    }
258
259
    /**
260
     * Get custom input string for debug purposes
261
     *
262
     * @return string
263
     */
264
    public function getCustomInput()
265
    {
266
        return $this->input;
267
    }
268
269
    /**
270
     * Get the ServerResponse of the last Command execution
271
     *
272
     * @return \Longman\TelegramBot\Entities\ServerResponse
273
     */
274
    public function getLastCommandResponse()
275
    {
276
        return $this->last_command_response;
277
    }
278
279
    /**
280
     * Handle getUpdates method
281
     *
282
     * @param int|null $limit
283
     * @param int|null $timeout
284
     *
285
     * @return \Longman\TelegramBot\Entities\ServerResponse
286
     */
287
    public function handleGetUpdates($limit = null, $timeout = null)
288
    {
289
        if (!DB::isDbConnected()) {
290
            return new Entities\ServerResponse([
291
                                                   'ok'          => false,
292
                                                   'description' => 'getUpdates needs MySQL connection!',
293
                                               ], $this->bot_name);
294
        }
295
296
        //DB Query
297
        $last_update = DB::selectTelegramUpdate(1);
298
299
        //As explained in the telegram bot api documentation
300
        $offset = (isset($last_update[0]['id'])) ? $last_update[0]['id'] + 1 : null;
301
302
        $response = Request::getUpdates([
303
                                            'offset'  => $offset,
304
                                            'limit'   => $limit,
305
                                            'timeout' => $timeout,
306
                                        ]);
307
308
        if ($response->isOk()) {
309
            //Process all updates
310
            foreach ((array)$response->getResult() as $result) {
311
                $this->processUpdate($result);
312
            }
313
        }
314
315
        return $response;
316
    }
317
318
    /**
319
     * Handle bot request from webhook
320
     *
321
     * @return bool
322
     *
323
     * @throws \Longman\TelegramBot\Exception\TelegramException
324
     */
325
    public function handle()
326
    {
327
        $this->input = Request::getInput();
328
329
        if (empty($this->input)) {
330
            throw new TelegramException('Input is empty!');
331
        }
332
        $post = json_decode($this->input, true);
333
        if (empty($post)) {
334
            throw new TelegramException('Invalid JSON!');
335
        }
336
337
        if ($response = $this->processUpdate(new Update($post, $this->bot_name))) {
338
            return $response->isOk();
339
        }
340
341
        return false;
342
    }
343
344
    /**
345
     * Get the command name from the command type
346
     *
347
     * @param string $type
348
     *
349
     * @return string
350
     */
351
    private function getCommandFromType($type)
352
    {
353
        return $this->ucfirstUnicode(str_replace('_', '', $type));
354
    }
355
356
    /**
357
     * Process bot Update request
358
     *
359
     * @param \Longman\TelegramBot\Entities\Update $update
360
     *
361
     * @return \Longman\TelegramBot\Entities\ServerResponse
362
     */
363
    public function processUpdate(Update $update)
364
    {
365
        $this->update = $update;
366
367
        //If all else fails, it's a generic message.
368
        $command = 'genericmessage';
369
370
        $update_type = $this->update->getUpdateType();
371
        if (in_array($update_type, ['inline_query', 'chosen_inline_result', 'callback_query', 'edited_message'])) {
372
            $command = $this->getCommandFromType($update_type);
373
        } elseif ($update_type === 'message') {
374
            $message = $this->update->getMessage();
375
376
            //Load admin commands
377
            if ($this->isAdmin()) {
378
                $this->addCommandsPath(BASE_COMMANDS_PATH . '/AdminCommands', false);
379
            }
380
381
            $this->addCommandsPath(BASE_COMMANDS_PATH . '/UserCommands', false);
382
383
            $type = $message->getType();
384
            if ($type === 'command') {
385
                $command = $message->getCommand();
386
            } elseif (in_array($type, [
387
                'channel_chat_created',
388
                'delete_chat_photo',
389
                'group_chat_created',
390
                'left_chat_member',
391
                'migrate_from_chat_id',
392
                'migrate_to_chat_id',
393
                'new_chat_member',
394
                'new_chat_photo',
395
                'new_chat_title',
396
                'supergroup_chat_created',
397
            ])) {
398
                $command = $this->getCommandFromType($type);
399
            }
400
        }
401
402
        //Make sure we have an up-to-date command list
403
        //This is necessary to "require" all the necessary command files!
404
        $this->getCommandsList();
405
406
        DB::insertRequest($this->update);
407
408
        return $this->executeCommand($command);
409
    }
410
411
    /**
412
     * Execute /command
413
     *
414
     * @param string $command
415
     *
416
     * @return mixed
417
     * @throws \Longman\TelegramBot\Exception\TelegramException
418
     */
419
    public function executeCommand($command)
420
    {
421
        $command_obj = $this->getCommandObject($command);
422
423
        if (!$command_obj || !$command_obj->isEnabled()) {
424
            //Failsafe in case the Generic command can't be found
425
            if ($command === 'Generic') {
426
                throw new TelegramException('Generic command missing!');
427
            }
428
429
            //Handle a generic command or non existing one
430
            $this->last_command_response = $this->executeCommand('Generic');
431
        } else {
432
            //Botan.io integration, make sure only the command user executed is reported
433
            if ($this->botan_enabled) {
434
                Botan::lock($command);
435
            }
436
437
            //execute() method is executed after preExecute()
438
            //This is to prevent executing a DB query without a valid connection
439
            $this->last_command_response = $command_obj->preExecute();
440
441
            //Botan.io integration, send report after executing the command
442
            if ($this->botan_enabled) {
443
                Botan::track($this->update, $command);
444
            }
445
        }
446
447
        return $this->last_command_response;
448
    }
449
450
    /**
451
     * Sanitize Command
452
     *
453
     * @param string $command
454
     *
455
     * @return string
456
     */
457 10
    protected function sanitizeCommand($command)
458
    {
459 10
        return str_replace(' ', '', $this->ucwordsUnicode(str_replace('_', ' ', $command)));
460
    }
461
462
    /**
463
     * Enable a single Admin account
464
     *
465
     * @param integer $admin_id Single admin id
466
     *
467
     * @return \Longman\TelegramBot\Telegram
468
     */
469 1
    public function enableAdmin($admin_id)
470
    {
471 1
        if (is_int($admin_id) && $admin_id > 0 && !in_array($admin_id, $this->admins_list)) {
472 1
            $this->admins_list[] = $admin_id;
473 1
        } else {
474 1
            TelegramLog::error('Invalid value "' . $admin_id . '" for admin.');
475
        }
476
477 1
        return $this;
478
    }
479
480
    /**
481
     * Enable a list of Admin Accounts
482
     *
483
     * @param array $admin_ids List of admin ids
484
     *
485
     * @return \Longman\TelegramBot\Telegram
486
     */
487 1
    public function enableAdmins(array $admin_ids)
488
    {
489 1
        foreach ($admin_ids as $admin_id) {
490 1
            $this->enableAdmin($admin_id);
491 1
        }
492
493 1
        return $this;
494
    }
495
496
    /**
497
     * Get list of admins
498
     *
499
     * @return array
500
     */
501 1
    public function getAdminList()
502
    {
503 1
        return $this->admins_list;
504
    }
505
506
    /**
507
     * Check if the passed user is an admin
508
     *
509
     * If no user id is passed, the current update is checked for a valid message sender.
510
     *
511
     * @param int|null $user_id
512
     *
513
     * @return bool
514
     */
515 11
    public function isAdmin($user_id = null)
516
    {
517 11
        if ($user_id === null && $this->update !== null) {
518
            if (($message = $this->update->getMessage()) && ($from = $message->getFrom())) {
519
                $user_id = $from->getId();
520
            } elseif (($inline_query = $this->update->getInlineQuery()) && ($from = $inline_query->getFrom())) {
521
                $user_id = $from->getId();
522
            } elseif (($chosen_inline_result = $this->update->getChosenInlineResult()) && ($from = $chosen_inline_result->getFrom())) {
523
                $user_id = $from->getId();
524
            } elseif (($callback_query = $this->update->getCallbackQuery()) && ($from = $callback_query->getFrom())) {
525
                $user_id = $from->getId();
526
            } elseif (($edited_message = $this->update->getEditedMessage()) && ($from = $edited_message->getFrom())) {
527
                $user_id = $from->getId();
528
            }
529
        }
530
531 11
        return ($user_id === null) ? false : in_array($user_id, $this->admins_list);
532
    }
533
534
    /**
535
     * Check if user required the db connection
536
     *
537
     * @return bool
538
     */
539
    public function isDbEnabled()
540
    {
541
        if ($this->mysql_enabled) {
542
            return true;
543
        } else {
544
            return false;
545
        }
546
    }
547
548
    /**
549
     * Add a single custom commands path
550
     *
551
     * @param string $path   Custom commands path to add
552
     * @param bool   $before If the path should be prepended or appended to the list
553
     *
554
     * @return \Longman\TelegramBot\Telegram
555
     */
556 39
    public function addCommandsPath($path, $before = true)
557
    {
558 39
        if (!is_dir($path)) {
559 1
            TelegramLog::error('Commands path "' . $path . '" does not exist.');
560 39
        } elseif (!in_array($path, $this->commands_paths)) {
561 39
            if ($before) {
562 39
                array_unshift($this->commands_paths, $path);
563 39
            } else {
564
                array_push($this->commands_paths, $path);
565
            }
566 39
        }
567
568 39
        return $this;
569
    }
570
571
    /**
572
     * Add multiple custom commands paths
573
     *
574
     * @param array $paths  Custom commands paths to add
575
     * @param bool  $before If the paths should be prepended or appended to the list
576
     *
577
     * @return \Longman\TelegramBot\Telegram
578
     */
579 1
    public function addCommandsPaths(array $paths, $before = true)
0 ignored issues
show
Unused Code introduced by
The parameter $before is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
580
    {
581 1
        foreach ($paths as $path) {
582 1
            $this->addCommandsPath($path);
583 1
        }
584
585 1
        return $this;
586
    }
587
588
    /**
589
     * Set custom upload path
590
     *
591
     * @param string $path Custom upload path
592
     *
593
     * @return \Longman\TelegramBot\Telegram
594
     */
595 39
    public function setUploadPath($path)
596
    {
597 39
        $this->upload_path = $path;
598 39
        return $this;
599
    }
600
601
    /**
602
     * Get custom upload path
603
     *
604
     * @return string
605
     */
606
    public function getUploadPath()
607
    {
608
        return $this->upload_path;
609
    }
610
611
    /**
612
     * Set custom download path
613
     *
614
     * @param string $path Custom download path
615
     *
616
     * @return \Longman\TelegramBot\Telegram
617
     */
618 39
    public function setDownloadPath($path)
619
    {
620 39
        $this->download_path = $path;
621 39
        return $this;
622
    }
623
624
    /**
625
     * Get custom download path
626
     *
627
     * @return string
628
     */
629
    public function getDownloadPath()
630
    {
631
        return $this->download_path;
632
    }
633
634
    /**
635
     * Set command config
636
     *
637
     * Provide further variables to a particular commands.
638
     * For example you can add the channel name at the command /sendtochannel
639
     * Or you can add the api key for external service.
640
     *
641
     * @param string $command
642
     * @param array  $config
643
     *
644
     * @return \Longman\TelegramBot\Telegram
645
     */
646 13
    public function setCommandConfig($command, array $config)
647
    {
648 13
        $this->commands_config[$command] = $config;
649 13
        return $this;
650
    }
651
652
    /**
653
     * Get command config
654
     *
655
     * @param string $command
656
     *
657
     * @return array
658
     */
659 24
    public function getCommandConfig($command)
660
    {
661 24
        return isset($this->commands_config[$command]) ? $this->commands_config[$command] : [];
662
    }
663
664
    /**
665
     * Get API key
666
     *
667
     * @return string
668
     */
669 1
    public function getApiKey()
670
    {
671 1
        return $this->api_key;
672
    }
673
674
    /**
675
     * Get Bot name
676
     *
677
     * @return string
678
     */
679 6
    public function getBotName()
680
    {
681 6
        return $this->bot_name;
682
    }
683
684
    /**
685
     * Get Version
686
     *
687
     * @return string
688
     */
689 1
    public function getVersion()
690
    {
691 1
        return $this->version;
692
    }
693
694
    /**
695
     * Set Webhook for bot
696
     *
697
     * @param string      $url
698
     * @param string|null $path_certificate
699
     *
700
     * @return \Longman\TelegramBot\Entities\ServerResponse
701
     * @throws \Longman\TelegramBot\Exception\TelegramException
702
     */
703
    public function setWebHook($url, $path_certificate = null)
704
    {
705
        if (empty($url)) {
706
            throw new TelegramException('Hook url is empty!');
707
        }
708
709
        $result = Request::setWebhook($url, $path_certificate);
710
711 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...
712
            throw new TelegramException(
713
                'Webhook was not set! Error: ' . $result->getErrorCode() . ' ' . $result->getDescription()
714
            );
715
        }
716
717
        return $result;
718
    }
719
720
    /**
721
     * Unset Webhook for bot
722
     *
723
     * @return mixed
724
     * @throws \Longman\TelegramBot\Exception\TelegramException
725
     */
726
    public function unsetWebHook()
727
    {
728
        $result = Request::setWebhook();
729
730 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...
731
            throw new TelegramException(
732
                'Webhook was not unset! Error: ' . $result->getErrorCode() . ' ' . $result->getDescription()
733
            );
734
        }
735
736
        return $result;
737
    }
738
739
    /**
740
     * Replace function `ucwords` for UTF-8 characters in the class definition and commands
741
     *
742
     * @param string $str
743
     * @param string $encoding (default = 'UTF-8')
744
     *
745
     * @return string
746
     */
747 10
    protected function ucwordsUnicode($str, $encoding = 'UTF-8')
748
    {
749 10
        return mb_convert_case($str, MB_CASE_TITLE, $encoding);
750
    }
751
752
    /**
753
     * Replace function `ucfirst` for UTF-8 characters in the class definition and commands
754
     *
755
     * @param string $str
756
     * @param string $encoding (default = 'UTF-8')
757
     *
758
     * @return string
759
     */
760 11
    protected function ucfirstUnicode($str, $encoding = 'UTF-8')
761
    {
762 11
        return mb_strtoupper(mb_substr($str, 0, 1, $encoding), $encoding) . mb_strtolower(mb_substr($str, 1,
763 11
                                                                                                    mb_strlen($str),
764 11
                                                                                                    $encoding),
765 11
                                                                                          $encoding);
766
    }
767
768
    /**
769
     * Enable Botan.io integration
770
     *
771
     * @param  $token
772
     * @return \Longman\TelegramBot\Telegram
773
     */
774
    public function enableBotan($token)
775
    {
776
        Botan::initializeBotan($token);
777
        $this->botan_enabled = true;
778
        return $this;
779
    }
780
}
781