Completed
Pull Request — master (#20)
by
unknown
03:12
created

CoreBot::editInlineMessageText()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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