Completed
Push — master ( b9a7c4...528803 )
by Danilo
03:15
created

CoreBot   D

Complexity

Total Complexity 41

Size/Duplication

Total Lines 674
Duplicated Lines 10.09 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 41
lcom 1
cbo 3
dl 68
loc 674
rs 4.5881
c 0
b 0
f 0

28 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 23 3
A getChatID() 0 5 1
A setChatID() 0 5 1
A getBotID() 0 17 3
A getMe() 0 5 1
A getUpdates() 0 11 1
A getWebhookInfo() 0 5 1
A deleteWebhook() 0 5 1
A setWebhook() 0 5 1
B setUpdateReturned() 0 42 3
A sendMessage() 0 15 1
A forwardMessage() 0 12 1
A sendPhoto() 0 13 1
A sendAudio() 0 17 1
A sendDocument() 0 14 1
A sendSticker() 0 13 1
A sendVoice() 15 15 1
A sendChatAction() 0 10 1
A getChat() 0 9 1
A getChatAdministrators() 0 9 1
A answerCallbackQuery() 0 18 2
A editMessageText() 14 14 1
A editInlineMessageText() 0 13 1
A editMessageReplyMarkup() 0 11 1
A answerInlineQuerySwitchPM() 20 20 2
A answerEmptyInlineQuerySwitchPM() 19 19 2
A apiRequest() 0 5 1
B exec_curl_request() 0 28 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like CoreBot often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CoreBot, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpBotFramework;
4
5
use Entities\InlineKeyboard;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, PhpBotFramework\InlineKeyboard.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
6
7
/**
8
 * \mainpage
9
 * \section Description
10
 * PhpBotFramework a lightweight framework for Telegram Bot API.
11
 * Designed to be fast and easy to use, it provides all the features a user need.
12
 * Take control of your bot using the command-handler system or the update type based function.
13
 *
14
 * \subsection Example
15
 * A quick example, the bot will send "Hello" every time the user click "/start":
16
 *
17
 *     <?php
18
 *
19
 *     // Include the framework
20
 *     require './vendor/autoload.php';
21
 *
22
 *     // Create the bot
23
 *     $bot = new DanySpin97\PhpBotFramework\Bot("token");
24
 *
25
 *     // Add a command that will be triggered every time the user click /start
26
 *     $bot->addMessageCommand("start",
27
 *         function($bot, $message) {
28
 *             $bot->sendMessage("Hello");
29
 *         }
30
 *     );
31
 *
32
 *     // Receive update from telegram using getUpdates
33
 *     $bot->getUpdatesLocal();
34
 *
35
 * \section Features
36
 * - Designed to be the fast and easy to use
37
 * - Support for getUpdates and webhooks
38
 * - Support for the most important API methods
39
 * - Command-handle system for messages and callback queries
40
 * - Update type based processing
41
 * - Easy inline keyboard creation
42
 * - Inline query results handler
43
 * - Sql database support
44
 * - Redis support
45
 * - Support for multilanguage bot
46
 * - Support for bot state
47
 * - Highly documented
48
 *
49
 * \section Requirements
50
 * - Php 7.0 or greater
51
 * - php-mbstring
52
 * - Composer (to install the framework)
53
 * - SSL certificate (<i>required by webhook</i>)
54
 * - Web server (<i>required by webhook</i>)
55
 *
56
 * \section Installation
57
 * In your project folder:
58
 *
59
 *     composer require danyspin97/php-bot-framework
60
 *     composer install --no-dev
61
 *
62
 * \subsection Web-server
63
 * To use webhook for the bot, a web server and a SSL certificate are required.
64
 * Install one using your package manager (nginx or caddy reccomended).
65
 * To get a SSL certificate you can user [Let's Encrypt](https://letsencrypt.org/).
66
 *
67
 * \section Usage
68
 * Add the scripting by adding command (Bot::addMessageCommand()) or by creating a class that inherits Bot.
69
 * Each api call will have <code>$_chat_id</code> set to the current user, use CoreBot::setChatID() to change it.
70
 *
71
 * \subsection getUpdates
72
 * The bot ask for updates to telegram server.
73
 * If you want to use getUpdates method to receive updates from telegram, add one of these function at the end of your bot:
74
 * - Bot::getUpdatesLocal()
75
 * - Bot::getUpdatesDatabase()
76
 * - Bot::getUpdatesRedis()
77
 *
78
 * The bot will process updates in a row, and will call Bot::processUpdate() for each.
79
 * getUpdates handling is single-threaded so there will be only one object that will process updates. The connection will be opened at the creation and used for the entire life of the bot.
80
 *
81
 * \subsection Webhook
82
 * A web server will create an instance of the bot for every update received.
83
 * If you want to use webhook call Bot::processWebhookUpdate() at the end of your bot. The bot will get data from <code>php://input</code> and process it using Bot::processUpdate().
84
 * Each instance of the bot will open its connection.
85
 *
86
 * \subsection Message-commands Message commands
87
 * Script how the bot will answer to messages containing commands (like <code>/start</code>).
88
 *
89
 *     $bot->addMessageCommand("start", function($bot, $message) {
90
 *             $bot->sendMessage("I am your personal bot, try /help command");
91
 *     });
92
 *
93
 *     $help_function = function($bot, $message) {
94
 *         $bot->sendMessage("This is the help message")
95
 *     };
96
 *
97
 *     $bot->addMessageCommand("/help", $help_function);
98
 *
99
 * Check Bot::addMessageCommand() for more.
100
 *
101
 * You can also use regex to check commands.
102
 *
103
 * The closure will be called if the commands if the expression evaluates to true. Here is an example:
104
 *
105
 *     $bot->addMessageCommandRegex("number\d",
106
 *         $help_function);
107
 *
108
 * The closure will be called when the user send a command that match the regex like, in this example, both <code>/number1</code> or <code>/number135</code>.
109
 *
110
 * \subsection Callback-commands Callback commands
111
 * Script how the bot will answer to callback query containing a particular string as data.
112
 *
113
 *     $bot->addCallbackCommand("back", function($bot, $callback_query) {
114
 *             $bot->editMessageText($callback_query['message']['message_id'], "You pressed back");
115
 *     });
116
 *
117
 * Check Bot::addCallbackCommand() for more.
118
 *
119
 * \subsection Bot-Intherited Inherit Bot Class
120
 * Create a new class that inherits Bot to handle all updates.
121
 *
122
 * <code>EchoBot.php</code>
123
 *
124
 *     // Create the class that will extends Bot class
125
 *     class EchoBot extends DanySpin97\PhpBotFramework\Bot {
126
 *
127
 *         // Add the function for processing messages
128
 *         protected function processMessage($message) {
129
 *
130
 *             // Answer each message with the text received
131
 *             $this->sendMessage($message['text']);
132
 *
133
 *         }
134
 *
135
 *     }
136
 *
137
 *     // Create an object of type EchoBot
138
 *     $bot = new EchoBot("token");
139
 *
140
 *     // Process updates using webhook
141
 *     $bot->processWebhookUpdate();
142
 *
143
 * Override these method to make your bot handle each update type:
144
 * - Bot::processMessage($message)
145
 * - Bot::processCallbackQuery($callback_query)
146
 * - Bot::processInlineQuery($inline_query)
147
 * - Bot::processChosenInlineResult($_chosen_inline_result)
148
 * - Bot::processEditedMessage($edited_message)
149
 * - Bot::processChannelPost($post)
150
 * - Bot::processEditedChannelPost($edited_post)
151
 *
152
 * \subsection InlineKeyboard-Usage InlineKeyboard Usage
153
 *
154
 * How to use the InlineKeyboard class:
155
 *
156
 *     // Create the bot
157
 *     $bot = new DanySpin97\PhpBotFramework\Bot("token");
158
 *
159
 *     $command_function = function($bot, $message) {
160
 *             // Add a button to the inline keyboard
161
 *             $bot->inline_keyboard->addLevelButtons([
162
 *                  // with written "Click me!"
163
 *                  'text' => 'Click me!',
164
 *                  // and that open the telegram site, if pressed
165
 *                  'url' => 'telegram.me'
166
 *                  ]);
167
 *             // Then send a message, with our keyboard in the parameter $reply_markup of sendMessage
168
 *             $bot->sendMessage("This is a test message", $bot->inline_keyboard->get());
169
 *             }
170
 *
171
 *     // Add the command
172
 *     $bot->addMessageCommand("start", $command_function);
173
 *
174
 * \subsection Sql-Database Sql Database
175
 * The sql database is used to save offset from getUpdates and to save user language.
176
 *
177
 * To connect a sql database to the bot, a pdo connection is required.
178
 *
179
 * Here is a simple pdo connection that is passed to the bot:
180
 *
181
 *     $bot->pdo = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
182
 *
183
 * \subsection Redis-database Redis Database
184
 * Redis is used to save offset from getUpdates, to store language (both as cache and persistent) and to save bot state.
185
 *
186
 * To connect redis with the bot, create a redis object.
187
 *
188
 *     $bot->redis = new Redis();
189
 *
190
 * \subsection Multilanguage-section Multilanguage Bot
191
 * This framework offers method to develop a multi language bot.
192
 *
193
 * Here's an example:
194
 *
195
 * <code>en.json</code>:
196
 *
197
 *     {"Greetings_Msg": "Hello"}
198
 *
199
 * <code>it.json</code>:
200
 *
201
 *     {"Greetings_Msg": "Ciao"}
202
 *
203
 * <code>Greetings.php</code>:
204
 *
205
 *     $bot->loadLocalization();
206
 *     $start_function = function($bot, $message) {
207
 *             $bot->sendMessage($this->localization[
208
 *                     $bot->getLanguageDatabase()]['Greetings_Msg'])
209
 *     };
210
 *
211
 *     $bot->addMessageCommand("start", $start_function);
212
 *
213
 * The bot will get the language from the database, then the bot will send the message localizated for the user.
214
 *
215
 * \ref Multilanguage [See here for more]
216
 *
217
 * \section Source
218
 * The source is hosted on github and can be found [here](https://github.com/DanySpin97/PhpBotFramework).
219
 *
220
 * \section Bot-created Bot using this framework
221
 * - [\@MyAddressBookBot](https://telegram.me/myaddressbookbot) ([Source](https://github.com/DanySpin97/MyAddressBookBot))
222
 * - [\@Giveaways_bot](https://telegram.me/giveaways_bot) ([Source](https://github.com/DanySpin97/GiveawaysBot))
223
 *
224
 * \section Authors
225
 * This framework is developed and manteined by Danilo Spinella.
226
 *
227
 * \section License
228
 * PhpBotFramework is released under GNU Lesser General Public License.
229
 * You may copy, distribute and modify the software provided that modifications are described and licensed for free under LGPL-3. Derivatives works (including modifications) can only be redistributed under LGPL-3, but applications that use the wrapper don't have to be.
230
 *
231
 */
232
233
/**
234
 * \class CoreBot
235
 * \brief Core of the framework
236
 * \details Contains data used by the bot to works, curl request handling, and all api methods (sendMessage, editMessageText, etc).
237
 */
238
class CoreBot {
239
240
    /**
241
     * \addtogroup Bot Bot
242
     * @{
243
     */
244
245
    /** \brief Chat_id of the user that interacted with the bot */
246
    protected $_chat_id;
247
248
    /** @} */
249
250
    /**
251
     * \addtogroup Core Core(Internal)
252
     * \brief Core of the framework.
253
     * @{
254
     */
255
256
    /** \brief The bot token (given by @BotFather). */
257
    private $token;
258
259
    /** \brief Url request (containing $token). */
260
    protected $_api_url;
261
262
    /** \brief Implements interface for execute HTTP requests. */
263
    protected $http;
264
265
    /** \brief Store id of the callback query received. */
266
    protected $_callback_query_id;
267
268
    /** \brief Store id of the inline query received. */
269
    protected $_inline_query_id;
270
271
    /**
272
     * \brief Contrusct an empty bot.
273
     * \details Construct a bot passing the token.
274
     * @param $token Token given by @botfather.
275
     */
276
    public function __construct(string $token) {
277
278
        // Check token is valid
279
        if (is_numeric($token) || $token === '') {
280
281
            throw new BotException('Token is not valid or empty');
282
283
        }
284
285
        // Init variables
286
        $this->token = $token;
287
        $this->_api_url = 'https://api.telegram.org/bot' . $token . '/';
288
289
        // Init connection and config it
290
        $this->http = new \GuzzleHttp\Client([
291
            'base_uri' => $this->_api_url,
292
            'connect_timeout' => 5,
293
            'verify' => false,
294
            'timeout' => 60
295
        ]);
296
297
        return;
298
    }
299
300
    /** @} */
301
302
    /**
303
     * \addtogroup Bot Bot
304
     * @{
305
     */
306
307
    /**
308
     * \brief Get chat id of the current user.
309
     * @return Chat id of the user.
310
     */
311
    public function getChatID() {
312
313
        return $this->_chat_id;
314
315
    }
316
317
    /**
318
     * \brief Set current chat id.
319
     * \details Change the chat id which the bot execute api methods.
320
     * @param $_chat_id The new chat id to set.
321
     */
322
    public function setChatID($_chat_id) {
323
324
        $this->_chat_id = $_chat_id;
325
326
    }
327
328
    /**
329
     * \brief Get bot ID using getMe API method.
330
     */
331
    public function getBotID() : int {
332
333
        // Get the id of the bot
334
        static $bot_id;
335
        $bot_id = ($this->getMe())['id'];
336
337
        // If it is not valid
338
        if (!isset($bot_id) || $bot_id == 0) {
339
340
            // get it again
341
            $bot_id = ($this->getMe())['id'];
342
343
        }
344
345
        return $bot_id ?? 0;
346
347
    }
348
349
    /** @} */
350
351
    /**
352
     * \addtogroup Api Api Methods
353
     * \brief All api methods to interface the bot with Telegram.
354
     * @{
355
     */
356
357
    /**
358
     * \brief A simple method for testing your bot's auth token.
359
     * \details Requires no parameters. Returns basic information about the bot in form of a User object. [Api reference](https://core.telegram.org/bots/api#getme)
360
     */
361
    public function getMe() {
362
363
        return $this->exec_curl_request('getMe?');
364
365
    }
366
367
368
    /**
369
     * \brief Request bot updates.
370
     * \details Request updates received by the bot using method getUpdates of Telegram API. [Api reference](https://core.telegram.org/bots/api#getupdates)
371
     * @param $offset <i>Optional</i>. Identifier of the first update to be returned. Must be greater by one than the highest among the identifiers of previously received updates. By default, updates starting with the earliest unconfirmed update are returned. An update is considered confirmed as soon as getUpdates is called with an offset higher than its update_id. The negative offset can be specified to retrieve updates starting from -offset update from the end of the updates queue. All previous updates will forgotten.
372
     * @param $limit <i>Optional</i>. Limits the number of updates to be retrieved. Values between 1—100 are accepted.
373
     * @param $timeout <i>Optional</i>. Timeout in seconds for long polling.
374
     * @return Array of updates (can be empty).
375
     */
376
    public function getUpdates(int $offset = 0, int $limit = 100, int $timeout = 60) {
377
378
        $parameters = [
379
            'offset' => $offset,
380
            'limit' => $limit,
381
            'timeout' => $timeout,
382
        ];
383
384
        return $this->exec_curl_request('getUpdates?' . http_build_query($parameters));
385
386
    }
387
388
    /**
389
     * \brief Get information about bot's webhook.
390
     * \details Returns an hash which contains information about bot's webhook.
391
     */
392
    public function getWebhookInfo() {
393
394
        return $this->exec_curl_request('getWebhookInfo');
395
396
    }
397
398
    /**
399
     * \brief Delete bot's webhook.
400
     * \details Delete bot's webhook if it exists.
401
     */
402
    public function deleteWebhook() {
403
404
        return $this->exec_curl_request('deleteWebhook');
405
406
    }
407
408
    /**
409
     * \brief Set bot's webhook.
410
     * \details Set a webhook for the current bot in order to receive incoming
411
     * updates via an outgoing webhook.
412
     * @param $params See [Telegram API](https://core.telegram.org/bots/api#setwebhook)
413
     * for more information about the available parameters.
414
     */
415
    public function setWebhook(array $params) {
416
417
        return $this->exec_curl_request('setWebhook?' . http_build_query($params));
418
419
    }
420
421
    /**
422
     * \brief Set updates received by the bot for getUpdates handling.
423
     * \details List the types of updates you want your bot to receive. For example, specify [“message”, “edited_channel_post”, “callback_query”] to only receive updates of these types. Specify an empty list to receive all updates regardless of type.
424
     * Set it one time and it won't change until next setUpdateReturned call.
425
     * @param $allowed_updates <i>Optional</i>. List of updates allowed.
426
     */
427
    public function setUpdateReturned(array $allowed_updates = []) {
428
429
        // Parameter for getUpdates
430
        $parameters = [
431
            'offset' => 0,
432
            'limit' => 1,
433
            'timeout' => 0,
434
        ];
435
436
        // Start the list
437
        $updates_string = '[';
438
439
        // Flag to skip adding ", " to the string
440
        $first_string = true;
441
442
        // Iterate over the list
443
        foreach ($allowed_updates as $index => $update) {
444
445
            // Is it the first update added?
446
            if (!$first_string) {
447
448
                $updates_string .= ', "' . $update . '"';
449
450
            } else {
451
452
                $updates_string .= '"' . $update . '"';
453
454
                // Set the flag to false cause we added an item
455
                $first_string = false;
456
457
            }
458
459
        }
460
461
        // Close string with the marker
462
        $updates_string .= ']';
463
464
        // Exec getUpdates
465
        $this->exec_curl_request('getUpdates?' . http_build_query($parameters)
466
                                               . '&allowed_updates=' . $updates_string);
467
468
    }
469
470
    /**
471
     * \brief Send a text message.
472
     * \details Use this method to send text messages. [Api reference](https://core.telegram.org/bots/api#sendmessage)
473
     * @param $text Text of the message.
474
     * @param $reply_markup <i>Optional</i>. Reply_markup of the message.
475
     * @param $parse_mode <i>Optional</i>. Parse mode of the message.
476
     * @param $disable_web_preview <i>Optional</i>. Disables link previews for links in this message.
477
     * @param $disable_notification <i>Optional</i>. Sends the message silently.
478
     * @return On success,  the sent message.
479
     */
480
    public function sendMessage($text, string $reply_markup = null, int $reply_to = null, string $parse_mode = 'HTML', bool $disable_web_preview = true, bool $disable_notification = false) {
481
482
        $parameters = [
483
            'chat_id' => $this->_chat_id,
484
            'text' => $text,
485
            'parse_mode' => $parse_mode,
486
            'disable_web_page_preview' => $disable_web_preview,
487
            'reply_markup' => $reply_markup,
488
            'reply_to_message_id' => $reply_to,
489
            'disable_notification' => $disable_notification
490
        ];
491
492
        return $this->exec_curl_request('sendMessage?' . http_build_query($parameters));
493
494
    }
495
496
    /**
497
     * \brief Forward a message.
498
     * \details Use this method to forward messages of any kind. [Api reference](https://core.telegram.org/bots/api#forwardmessage)
499
     * @param $from_chat_id The chat where the original message was sent.
500
     * @param $message_id Message identifier (id).
501
     * @param $disable_notification <i>Optional</i>. Sends the message silently.
502
     * @return On success,  the sent message.
503
     */
504
    public function forwardMessage($from_chat_id, int $message_id, bool $disable_notification = false) {
505
506
        $parameters = [
507
            'chat_id' => $this->_chat_id,
508
            'message_id' => $message_id,
509
            'from_chat_id' => $from_chat_id,
510
            'disable_notification' => $disable_notification
511
        ];
512
513
        return $this->exec_curl_request('forwardMessage?' . http_build_query($parameters));
514
515
    }
516
517
    /**
518
     * \brief Send a photo.
519
     * \details Use this method to send photos. [Api reference](https://core.telegram.org/bots/api#sendphoto)
520
     * @param $photo Photo to send, can be a file_id or a string referencing the location of that image.
521
     * @param $reply_markup <i>Optional</i>. Reply markup of the message.
522
     * @param $caption <i>Optional</i>. Photo caption (may also be used when resending photos by file_id), 0-200 characters.
523
     * @param $disable_notification <i>Optional<i>. Sends the message silently.
524
     * @return On success,  the sent message.
525
     */
526
    public function sendPhoto($photo, string $reply_markup = null, string $caption = '', bool $disable_notification = false) {
527
528
        $parameters = [
529
            'chat_id' => $this->_chat_id,
530
            'photo' => $photo,
531
            'caption' => $caption,
532
            'reply_markup' => $reply_markup,
533
            'disable_notification' => $disable_notification,
534
        ];
535
536
        return $this->exec_curl_request('sendPhoto?' . http_build_query($parameters));
537
538
    }
539
540
    /**
541
     * \brief Send an audio.
542
     * \details Use this method to send audio files, if you want Telegram clients to display them in the music player. Your audio must be in the .mp3 format. [Api reference](https://core.telegram.org/bots/api/#sendaudio)
543
     * Bots can currently send audio files of up to 50 MB in size, this limit may be changed in the future.
544
     * @param $audio Audio file to send. Pass a file_id as String to send an audio file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an audio file from the Internet, or upload a new one using multipart/form-data.
545
     * @param $caption <i>Optional</i>. Audio caption, 0-200 characters.
546
     * @param $reply_markup <i>Optional</i>. Reply markup of the message.
547
     * @param $duration <i>Optional</i>. Duration of the audio in seconds.
548
     * @param $performer <i>Optional</i>. Performer.
549
     * @param $title <i>Optional</i>. Track name.
550
     * @param $disable_notification <i>Optional</i>. Sends the message silently.
551
     * @param $reply_to_message_id <i>Optional</i>. If the message is a reply, ID of the original message.
552
     * @return On success, the sent message.
553
     */
554
    public function sendAudio($audio, string $caption = null, string $reply_markup = null, int $duration = null, string $performer, string $title = null, bool $disable_notification = false, int $reply_to_message_id = null) {
555
556
        $parameters = [
557
            'chat_id' => $this->_chat_id,
558
            'audio' => $audio,
559
            'caption' => $caption,
560
            'duration' => $duration,
561
            'performer' => $performer,
562
            'title' => $title,
563
            'reply_to_message_id' => $reply_to_message_id,
564
            'reply_markup' => $reply_markup,
565
            'disable_notification' => $disable_notification,
566
        ];
567
568
        return $this->exec_curl_request('sendAudio?' . http_build_query($parameters));
569
570
    }
571
572
    /**
573
     * \brief Send a document.
574
     * \details Use this method to send general files. [Api reference](https://core.telegram.org/bots/api/#senddocument)
575
     * @param $document File to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data.
576
     * @param <i>Optional</i>. Document caption (may also be used when resending documents by file_id), 0-200 characters.
577
     *
578
     * @param $reply_markup <i>Optional</i>. Reply markup of the message.
579
     * @param <i>Optional</i>. Sends the message silently.
580
     * @param <i>Optional</i>. If the message is a reply, ID of the original message.
581
     */
582
    public function sendDocument($document, string $caption = '', string $reply_markup = null, bool $disable_notification = false, int $reply_to_message_id = null) {
583
584
        $parameters = [
585
            'chat_id' => $this->_chat_id,
586
            'document' => $document,
587
            'caption' => $caption,
588
            'reply_to_message_id' => $reply_to_message_id,
589
            'reply_markup' => $reply_markup,
590
            'disable_notification' => $disable_notification,
591
        ];
592
593
        return $this->exec_curl_request('sendAudio?' . http_build_query($parameters));
594
595
    }
596
597
598
    /**
599
     * \brief Send a sticker
600
     * \details Use this method to send .webp stickers. [Api reference](https://core.telegram.org/bots/api/#sendsticker)
601
     * @param $sticker Sticker to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a .webp file from the Internet, or upload a new one using multipart/form-data.
602
     * @param $reply_markup <i>Optional</i>. Reply markup of the message.
603
     * @param $disable_notification Sends the message silently.
604
     * @param <i>Optional</i>. If the message is a reply, ID of the original message.
605
     * @param On success, the sent message.
606
     */
607
    public function sendSticker($sticker, string $reply_markup = null, bool $disable_notification = false, int $reply_to_message_id = null) {
608
609
        $parameters = [
610
            'chat_id' => $this->_chat_id,
611
            'sticker' => $sticker,
612
            'disable_notification' => $disable_notification,
613
            'reply_to_message_id' => $reply_to_message_id,
614
            'reply_markup' => $reply_markup
615
        ];
616
617
        return $this->exec_curl_request('sendSticker?' . http_build_query($parameters));
618
619
    }
620
621
    /**
622
     * \brief Send audio files.
623
     * \details Use this method to send audio files, if you want Telegram clients to display the file as a playable voice message. For this to work, your audio must be in an .ogg file encoded with OPUS (other formats may be sent as Audio or Document).o
624
     * Bots can currently send voice messages of up to 50 MB in size, this limit may be changed in the future.
625
     * @param $voice Audio file to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data.
626
     * @param $caption <i>Optional</i>. Voice message caption, 0-200 characters
627
     * @param $duration <i>Optional</i>. Duration of the voice message in seconds
628
     * @param $reply_markup <i>Optional</i>. Reply markup of the message.
629
     * @param $disable_notification <i>Optional</i>. Sends the message silently.
630
     * @param $reply_to_message_id <i>Optional</i>. If the message is a reply, ID of the original message.
631
     * @return On success, the sent message is returned.
632
     */
633 View Code Duplication
    public function sendVoice($voice, string $caption, int $duration, string $reply_markup = null, bool $disable_notification, int $reply_to_message_id = 0) {
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...
634
635
        $parameters = [
636
            'chat_id' => $this->_chat_id,
637
            'voice' => $voice,
638
            'caption' => $caption,
639
            'duration' => $duration,
640
            'disable_notification', $disable_notification,
641
            'reply_to_message_id' => $reply_to_message_id,
642
            'reply_markup' => $reply_markup
643
        ];
644
645
        return $this->exec_curl_request('sendVoice?' . http_build_query($parameters));
646
647
    }
648
649
    /**
650
     * \brief Say the user what action is the bot doing.
651
     * \details Use this method when you need to tell the user that something is happening on the bot's side. The status is set for 5 seconds or less (when a message arrives from your bot, Telegram clients clear its typing status). [Api reference](https://core.telegram.org/bots/api#sendchataction)
652
     * @param $action Type of action to broadcast. Choose one, depending on what the user is about to receive:
653
     * - <code>typing</code> for text messages
654
     * - <code>upload_photo</code> for photos
655
     * - <code>record_video</code> or <code>upload_video</code> for videos
656
     * - <code>record_audio</code> or <code>upload_audio</code> for audio files
657
     * - <code>upload_document</code> for general files
658
     * - <code>find_location</code> for location data
659
     * @return True on success.
660
     */
661
    public function sendChatAction(string $action) : bool {
662
663
        $parameters = [
664
            'chat_id' => $this->_chat_id,
665
            'action' => $action
666
        ];
667
668
        return $this->exec_curl_request('sendChatAction?' . http_build_query($parameters));
669
670
    }
671
672
    /**
673
     * \brief Get info about a chat.
674
     * \details Use this method to get up to date information about the chat (current name of the user for one-on-one conversations, current username of a user, group or channel, etc.). [Api reference](https://core.telegram.org/bots/api#getchat)
675
     * @param Unique identifier for the target chat or username of the target supergroup or channel (in the format <code>@channelusername</code>)
676
     */
677
    public function getChat($_chat_id) {
678
679
        $parameters = [
680
            'chat_id' => $_chat_id,
681
        ];
682
683
        return $this->exec_curl_request('getChat?' . http_build_query($parameters));
684
685
    }
686
687
    /**
688
     * \brief Use this method to get a list of administrators in a chat.
689
     * @param Unique identifier for the target chat or username of the target supergroup or channel (in the format <code>@channelusername</code>)
690
     * @return On success, returns an Array of ChatMember objects that contains information about all chat administrators except other bots. If the chat is a group or a supergroup and no administrators were appointed, only the creator will be returned.
691
     */
692
    public function getChatAdministrators($_chat_id) {
693
694
        $parameters = [
695
            'chat_id' => $_chat_id,
696
        ];
697
698
        return $this->exec_curl_request('getChatAdministrators?' . http_build_query($parameters));
699
700
    }
701
702
703
    /* \brief Answer a callback query
704
     * \details Remove the updating cirle on an inline keyboard button and showing a message/alert to the user.
705
     * It will always answer the current callback query.
706
     * @param $text <i>Optional</i>. Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters.
707
     * @param $show_alert <i>Optional</i>. If true, an alert will be shown by the client instead of a notification at the top of the chat screen.
708
     * @param $url <i>Optional</i>. URL that will be opened by the user's client. If you have created a Game and accepted the conditions via @Botfather, specify the URL that opens your game – note that this will only work if the query comes from a callback_game button.
709
     * Otherwise, you may use links like telegram.me/your_bot?start=XXXX that open your bot with a parameter.
710
     * @return True on success.
711
     */
712
    public function answerCallbackQuery($text = '', $show_alert = false, string $url = '') : bool {
713
714
        if (!isset($this->_callback_query_id)) {
715
716
            throw new BotException("Callback query id not set, wrong update");
717
718
        }
719
720
        $parameters = [
721
            'callback_query_id' => $this->_callback_query_id,
722
            'text' => $text,
723
            'show_alert' => $show_alert,
724
            'url' => $url
725
        ];
726
727
        return $this->exec_curl_request('answerCallbackQuery?' . http_build_query($parameters));
728
729
    }
730
731
    /**
732
     * \brief Edit text of a message sent by the bot.
733
     * \details Use this method to edit text and game messages sent by the bot. [Api reference](https://core.telegram.org/bots/api#editmessagetext)
734
     * @param $message_id Unique identifier of the sent message.
735
     * @param $text New text of the message.
736
     * @param $reply_markup Reply markup of the message will have (will be removed if this is null).
737
     * @param $parse_mode <i>Optional</i>. Send Markdown or HTML.
738
     * @param $disable_web_preview <i>Optional</i>. Disables link previews for links in this message.
739
     */
740 View Code Duplication
    public function editMessageText(int $message_id, $text, $reply_markup = null, string $parse_mode = 'HTML', bool $disable_web_preview = true) {
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...
741
742
        $parameters = [
743
            'chat_id' => $this->_chat_id,
744
            'message_id' => $message_id,
745
            'text' => $text,
746
            'reply_markup' => $reply_markup,
747
            'parse_mode' => $parse_mode,
748
            'disable_web_page_preview' => $disable_web_preview,
749
        ];
750
751
        return $this->exec_curl_request('editMessageText?' . http_build_query($parameters));
752
753
    }
754
755
    /**
756
     * \brief Edit text of a message sent via the bot.
757
     * \details Use this method to edit text messages sent via the bot (for inline queries). [Api reference](https://core.telegram.org/bots/api#editmessagetext)
758
     * @param $inline_message_id  Identifier of the inline message.
759
     * @param $text New text of the message.
760
     * @param $reply_markup Reply markup of the message will have (will be removed if this is null).
761
     * @param $parse_mode <i>Optional</i>. Send Markdown or HTML.
762
     * @param $disable_web_preview <i>Optional</i>. Disables link previews for links in this message.
763
     */
764
    public function editInlineMessageText(string $inline_message_id, $text, string $reply_markup = null, string $parse_mode = 'HTML', bool $disable_web_preview = false) {
765
766
        $parameters = [
767
            'inline_message_id' => $inline_message_id,
768
            'text' => $text,
769
            'reply_markup' => $reply_markup,
770
            'parse_mode' => $parse_mode,
771
            'disable_web_page_preview' => $disable_web_preview,
772
        ];
773
774
        return $this->exec_curl_request('editMessageText?' . http_build_query($parameters));
775
776
    }
777
778
    /*
779
     * Edit only the inline keyboard of a message (https://core.telegram.org/bots/api#editmessagereplymarkup)ù
780
     * @param
781
     * $message_id Identifier of the message to edit
782
     * $inline_keyboard Inlike keyboard array (https://core.telegram.org/bots/api#inlinekeyboardmarkup)
783
     */
784
    public function editMessageReplyMarkup($message_id, $inline_keyboard) {
785
786
        $parameters = [
787
            'chat_id' => $this->_chat_id,
788
            'message_id' => $message_id,
789
            'reply_markup' => $inline_keyboard,
790
        ];
791
792
        return $this->exec_curl_request('editMessageReplyMarkup?' . http_build_query($parameters));
793
794
    }
795
796
    /*
797
     * Answer a inline query (when the user write @botusername "Query") with a button, that will make user switch to the private chat with the bot, on the top of the results (https://core.telegram.org/bots/api#answerinlinequery)
798
     * @param
799
     * $results Array on InlineQueryResult (https://core.telegram.org/bots/api#inlinequeryresult)
800
     * $switch_pm_text Text to show on the button
801
     */
802 View Code Duplication
    public function answerInlineQuerySwitchPM($results, $switch_pm_text, $switch_pm_parameter = '', $is_personal = true, $cache_time = 300) {
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...
803
804
        if (!isset($this->_inline_query_id)) {
805
806
            throw new BotException("Inline query id not set, wrong update");
807
808
        }
809
810
        $parameters = [
811
            'inline_query_id' => $this->_inline_query_id,
812
            'switch_pm_text' => $switch_pm_text,
813
            'is_personal' => $is_personal,
814
            'switch_pm_parameter' => $switch_pm_parameter,
815
            'results' => $results,
816
            'cache_time' => $cache_time
817
        ];
818
819
        return $this->exec_curl_request('answerInlineQuery?' . http_build_query($parameters));
820
821
    }
822
823
    /*
824
     * Answer a inline query (when the user write @botusername "Query") with a button, that will make user switch to the private chat with the bot, on the top of the results (https://core.telegram.org/bots/api#answerinlinequery)
825
     * without showing any results to the user
826
     * @param
827
     * $switch_pm_text Text to show on the button
828
     */
829 View Code Duplication
    public function answerEmptyInlineQuerySwitchPM($switch_pm_text, $switch_pm_parameter = '', $is_personal = true, $cache_time = 300) {
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...
830
831
        if (!isset($this->_inline_query_id)) {
832
833
            throw new BotException("Inline query id not set, wrong update");
834
835
        }
836
837
        $parameters = [
838
            'inline_query_id' => $this->_inline_query_id,
839
            'switch_pm_text' => $switch_pm_text,
840
            'is_personal' => $is_personal,
841
            'switch_pm_parameter' => $switch_pm_parameter,
842
            'cache_time' => $cache_time
843
        ];
844
845
        return $this->exec_curl_request('answerInlineQuery?' . http_build_query($parameters));
846
847
    }
848
849
    /**
850
     * \brief Exec any api request using this method.
851
     * \details Use this method for custom api calls using this syntax:
852
     *
853
     *     $param = [
854
     *             'chat_id' => $_chat_id,
855
     *             'text' => 'Hello!'
856
     *     ];
857
     *     apiRequest("sendMessage", $param);
858
     *
859
     * @param $method The method to call.
860
     * @param $parameters Parameters to add.
861
     * @return Depends on api method.
862
     */
863
    public function apiRequest(string $method, array $parameters) {
864
865
        return $this->exec_curl_request($method . '?' . http_build_query($parameters));
866
867
    }
868
869
    /** @} */
870
871
    /**
872
     * \addtogroup Core Core(internal)
873
     * @{
874
     */
875
876
    /** \brief Core function to execute url request.
877
     * @param $url The url to call using the curl session.
878
     * @return Url response, false on error.
879
     */
880
    protected function exec_curl_request($url, $method = 'POST') {
881
882
        $response = $this->http->request($method, $url);
883
        $http_code = $response->getStatusCode();
884
885
        if ($http_code === 200) {
886
            $response = json_decode($response->getBody(), true);
887
888
            if (isset($response['desc'])) {
889
                error_log("Request was successfull: {$response['description']}\n");
890
            }
891
892
            return $response['result'];
893
        } elseif ($http_code >= 500) {
894
            // do not wat to DDOS server if something goes wrong
895
            sleep(10);
896
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by PhpBotFramework\CoreBot::exec_curl_request of type PhpBotFramework\Url.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
897
        } else {
898
            $response = json_decode($response->getBody(), true);
899
            error_log("Request has failed with error {$response['error_code']}: {$response['description']}\n");
900
            if ($http_code === 401) {
901
                throw new BotException('Invalid access token provided');
902
            }
903
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by PhpBotFramework\CoreBot::exec_curl_request of type PhpBotFramework\Url.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
904
        }
905
906
        return $response;
0 ignored issues
show
Unused Code introduced by
return $response; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
907
    }
908
909
    /** @} */
910
911
}
912
913