Completed
Push — master ( 0a4bed...fb98c7 )
by Danilo
02:11
created

CoreBot   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 586
Duplicated Lines 14.51 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 33
c 0
b 0
f 0
lcom 1
cbo 1
dl 85
loc 586
rs 9.3999

23 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 24 3
A __destruct() 0 6 1
A getChatID() 0 5 1
A setChatID() 0 5 1
A getUpdates() 0 11 1
B setUpdateReturned() 0 41 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() 10 10 1
A getChat() 9 9 1
A answerCallbackQuery() 0 12 1
A editMessageText() 14 14 1
A editInlineMessageText() 0 13 1
A editMessageReplyMarkup() 11 11 1
A answerInlineQuerySwitchPM() 14 14 1
A answerEmptyInlineQuerySwitchPM() 12 12 1
A apiRequest() 0 5 1
C exec_curl_request() 0 37 7

How to fix   Duplicated Code   

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:

1
<?php
2
3
namespace DanySpin97\PhpBotFramework;
4
5
6
/**
7
 * \mainpage
8
 * \section Description
9
 * PhpBotFramework a lightweight framework for Telegram Bot API.
10
 * Designed to be fast and easy to use, it provides all the features a user need.
11
 * Take control of your bot using the command-handler system or the update type based function.
12
 *
13
 * \subsection Example
14
 * A quick example, the bot will send "Hello" every time the user click "/start":
15
 *
16
 *     <?php
17
 *
18
 *     // Include the framework
19
 *     require './vendor/autoload.php';
20
 *
21
 *     // Create the bot
22
 *     $bot = new DanySpin97\PhpBotFramework\Bot("token");
23
 *
24
 *     // Add a command that will be triggered every time the user click /start
25
 *     $bot->addMessageCommand("start",
26
 *         function($bot, $message) {
27
 *             $bot->sendMessage("Hello");
28
 *         }
29
 *     );
30
 *
31
 *     // Receive update from telegram using getUpdates
32
 *     $bot->getUpdatesLocal();
33
 *
34
 * \section Features
35
 * - Designed to be the fast and easy to use
36
 * - Support for getUpdates and webhooks
37
 * - Support for the most important API methods
38
 * - Command-handle system for messages and callback queries
39
 * - Update type based processing
40
 * - Easy inline keyboard creation
41
 * - Inline query results handler
42
 * - Sql database support
43
 * - Redis support
44
 * - Support for multilanguage bot
45
 * - Support for bot state
46
 * - Highly documented
47
 *
48
 * \section Requirements
49
 * - Php 7.0 or greater
50
 * - php-mbstring
51
 * - Composer (to install the framework)
52
 * - SSL certificate (<i>required by webhook</i>)
53
 * - Web server (<i>required by webhook</i>)
54
 *
55
 * \section Installation
56
 * In your project folder:
57
 *
58
 *     composer require danyspin97/php-bot-framework
59
 *     composer install --no-dev
60
 *
61
 * \subsection Web-server
62
 * To use webhook for the bot, a web server and a SSL certificate are required.
63
 * Install one using your package manager (nginx or caddy reccomended).
64
 * To get a SSL certificate you can user [Let's Encrypt](https://letsencrypt.org/).
65
 *
66
 * \section Usage
67
 * Add the scripting by adding command (Bot::addMessageCommand()) or by creating a class that inherits Bot.
68
 * Each api call will have <code>$chat_id</code> set to the current user, use CoreBot::setChatID() to change it.
69
 *
70
 * \subsection getUpdates
71
 * The bot ask for updates to telegram server.
72
 * If you want to use getUpdates method to receive updates from telegram, add one of these function at the end of your bot:
73
 * - Bot::getUpdatesLocal()
74
 * - Bot::getUpdatesDatabase()
75
 * - Bot::getUpdatesRedis()
76
 *
77
 * The bot will process updates in a row, and will call Bot::processUpdate() for each.
78
 * 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.
79
 *
80
 * \subsection Webhook
81
 * A web server will create an instance of the bot for every update received.
82
 * 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().
83
 * Each instance of the bot will open its connection.
84
 *
85
 * \subsection Message-commands Message commands
86
 * Script how the bot will answer to messages containing commands (like <code>/start</code>).
87
 *
88
 *     $bot->addMessageCommand("start", function($bot, $message) {
89
 *             $bot->sendMessage("I am your personal bot, try /help command");
90
 *     });
91
 *
92
 *     $help_function = function($bot, $message) {
93
 *         $bot->sendMessage("This is the help message")
94
 *     };
95
 *
96
 *     $bot->addMessageCommand("/help", $help_function);
97
 *
98
 * Check Bot::addMessageCommand() for more.
99
 *
100
 * You can also use regex to check commands.
101
 *
102
 * The closure will be called if the commands if the expression evaluates to true. Here is an example:
103
 *
104
 *     $bot->addMessageCommandRegex("number\d",
105
 *         $help_function);
106
 *
107
 * 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>.
108
 *
109
 * \subsection Callback-commands Callback commands
110
 * Script how the bot will answer to callback query containing a particular string as data.
111
 *
112
 *     $bot->addCallbackCommand("back", function($bot, $callback_query) {
113
 *             $bot->editMessageText($callback_query['message']['message_id'], "You pressed back");
114
 *     });
115
 *
116
 * Check Bot::addCallbackCommand() for more.
117
 *
118
 * \subsection Bot-Intherited Inherit Bot Class
119
 * Create a new class that inherits Bot to handle all updates.
120
 *
121
 * <code>EchoBot.php</code>
122
 *
123
 *     // Create the class that will extends Bot class
124
 *     class EchoBot extends DanySpin97\PhpBotFramework\Bot {
125
 *
126
 *         // Add the function for processing messages
127
 *         protected function processMessage($message) {
128
 *
129
 *             // Answer each message with the text received
130
 *             $this->sendMessage($message['text']);
131
 *
132
 *         }
133
 *
134
 *     }
135
 *
136
 *     // Create an object of type EchoBot
137
 *     $bot = new EchoBot("token");
138
 *
139
 *     // Process updates using webhook
140
 *     $bot->processWebhookUpdate();
141
 *
142
 * Override these method to make your bot handle each update type:
143
 * - Bot::processMessage($message)
144
 * - Bot::processCallbackQuery($callback_query)
145
 * - Bot::processInlineQuery($inline_query)
146
 * - Bot::processChosenInlineResult($chosen_inline_result)
147
 * - Bot::processEditedMessage($edited_message)
148
 * - Bot::processChannelPost($post)
149
 * - Bot::processEditedChannelPost($edited_post)
150
 *
151
 * \subsection InlineKeyboard-Usage InlineKeyboard Usage
152
 *
153
 * How to use the InlineKeyboard class:
154
 *
155
 *     // Create the bot
156
 *     $bot = new DanySpin97\PhpBotFramework\Bot("token");
157
 *
158
 *     $command_function = function($bot, $message) {
159
 *             // Add a button to the inline keyboard
160
 *             $bot->inline_keyboard->addLevelButtons([
161
 *                  // with written "Click me!"
162
 *                  'text' => 'Click me!',
163
 *                  // and that open the telegram site, if pressed
164
 *                  'url' => 'telegram.me'
165
 *                  ]);
166
 *             // Then send a message, with our keyboard in the parameter $reply_markup of sendMessage
167
 *             $bot->sendMessage("This is a test message", $bot->inline_keyboard->get());
168
 *             }
169
 *
170
 *     // Add the command
171
 *     $bot->addMessageCommand("start", $command_function);
172
 *
173
 * \subsection Sql-Database Sql Database
174
 * The sql database is used to save offset from getUpdates and to save user language.
175
 *
176
 * To connect a sql database to the bot, a pdo connection is required.
177
 *
178
 * Here is a simple pdo connection that is passed to the bot:
179
 *
180
 *     $bot->pdo = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
181
 *
182
 * \subsection Redis-database Redis Database
183
 * Redis is used to save offset from getUpdates, to store language (both as cache and persistent) and to save bot state.
184
 *
185
 * To connect redis with the bot, create a redis object.
186
 *
187
 *     $bot->redis = new Redis();
188
 *
189
 * \subsection Multilanguage-section Multilanguage Bot
190
 * This framework offers method to develop a multi language bot.
191
 *
192
 * Here's an example:
193
 *
194
 * <code>en.json</code>:
195
 *
196
 *     {"Greetings_Msg": "Hello"}
197
 *
198
 * <code>it.json</code>:
199
 *
200
 *     {"Greetings_Msg": "Ciao"}
201
 *
202
 * <code>Greetings.php</code>:
203
 *
204
 *     $bot->loadLocalization();
205
 *     $start_function = function($bot, $message) {
206
 *             $bot->sendMessage($this->localization[
207
 *                     $bot->getLanguageDatabase()]['Greetings_Msg'])
208
 *     };
209
 *
210
 *     $bot->addMessageCommand("start", $start_function);
211
 *
212
 * The bot will get the language from the database, then the bot will send the message localizated for the user.
213
 *
214
 * \ref Multilanguage [See here for more]
215
 *
216
 * \section Source
217
 * The source is hosted on github and can be found [here](https://github.com/DanySpin97/PhpBotFramework).
218
 *
219
 * \section Bot-created Bot using this framework
220
 * - [\@MyAddressBookBot](https://telegram.me/myaddressbookbot) ([Source](https://github.com/DanySpin97/MyAddressBookBot))
221
 * - [\@Giveaways_bot](https://telegram.me/giveaways_bot) ([Source](https://github.com/DanySpin97/GiveawaysBot))
222
 *
223
 * \section Authors
224
 * This framework is developed and manteined by Danilo Spinella.
225
 *
226
 * \section License
227
 * PhpBotFramework is released under GNU Lesser General Public License.
228
 * 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.
229
 *
230
 */
231
232
/**
233
 * \class CoreBot
234
 * \brief Core of the framework
235
 * \details Contains data used by the bot to works, curl request handling, and all api methods (sendMessage, editMessageText, etc).
236
 */
237
class CoreBot {
238
239
    /**
240
     * \addtogroup Bot Bot
241
     * @{
242
     */
243
244
    /** \brief Chat_id of the user that interacted with the bot */
245
    protected $chat_id;
246
247
    /** @} */
248
249
    /**
250
     * \addtogroup Core Core(Internal)
251
     * \brief Core of the framework.
252
     * @{
253
     */
254
255
    /** \brief The bot token (given by @BotFather). */
256
    private $token;
257
258
    /** \brief Url request (containing $token). */
259
    protected $api_url;
260
261
    /** \brief Curl connection for request. */
262
    public $ch;
263
264
265
    /**
266
     * \brief Contrusct an empty bot.
267
     * \details Construct a bot passing the token.
268
     * @param $token Token given by @botfather.
269
     */
270
    public function __construct(string $token) {
271
272
        // Check token is valid
273
        if (is_numeric($token) || $token === '') {
274
            throw new BotException('Token is not valid or empty');
275
            return;
0 ignored issues
show
Unused Code introduced by
return; 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...
276
        }
277
278
        // Init variables
279
        $this->token = $token;
280
        $this->api_url = 'https://api.telegram.org/bot' . $token . '/';
281
282
        // Init connection and config it
283
        $this->ch = curl_init();
284
        curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, 1);
285
        curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, 5);
286
        curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, false);
287
        curl_setopt($this->ch, CURLOPT_TIMEOUT, 60);
288
        curl_setopt($this->ch, CURLOPT_HEADER, 0);
289
        curl_setopt($this->ch, CURLOPT_ENCODING, '');
290
        // DEBUG
291
        //curl_setopt($this->ch, CURLOPT_VERBOSE, true);
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
292
293
    }
294
295
    /** \brief Destroy the object. */
296
    public function __destruct() {
297
298
        // Close connection
299
        curl_close($this->ch);
300
301
    }
302
303
    /** @} */
304
305
    /**
306
     * \addtogroup Bot Bot
307
     * @{
308
     */
309
310
    /**
311
     * \brief Get chat id of the current user.
312
     * @return Chat id of the user.
313
     */
314
    public function getChatID() {
315
316
        return $this->chat_id;
317
318
    }
319
320
    /**
321
     * \brief Set current chat id.
322
     * \details Change the chat id which the bot execute api methods.
323
     * @param $chat_id The new chat id to set.
324
     */
325
    public function setChatID($chat_id) {
326
327
        $this->chat_id = $chat_id;
328
329
    }
330
331
    /** @} */
332
333
    /**
334
     * \addtogroup Api Api Methods
335
     * \brief All api methods to interface the bot with Telegram.
336
     * @{
337
     */
338
339
    /**
340
     * \brief Request bot updates.
341
     * \details Request updates received by the bot using method getUpdates of Telegram API. [Api reference](https://core.telegram.org/bots/api#getupdates)
342
     * @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.
343
     * @param $limit <i>Optional</i>. Limits the number of updates to be retrieved. Values between 1—100 are accepted.
344
     * @param $timeout <i>Optional</i>. Timeout in seconds for long polling.
345
     * @return Array of updates (can be empty).
346
     */
347
    public function getUpdates(int $offset = 0, int $limit = 100, int $timeout = 60) {
348
349
        $parameters = [
350
            'offset' => $offset,
351
            'limit' => $limit,
352
            'timeout' => $timeout,
353
        ];
354
355
        return $this->exec_curl_request($this->api_url . 'getUpdates?' . http_build_query($parameters));
356
357
    }
358
359
    /**
360
     * \brief Set updates received by the bot for getUpdates handling.
361
     * \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.
362
     * Set it one time and it won't change until next setUpdateReturned call.
363
     * @param $allowed_updates <i>Optional</i>. List of updates allowed.
364
     */
365
    public function setUpdateReturned(array $allowed_updates = []) {
366
367
        // Parameter for getUpdates
368
        $parameters = [
369
            'offset' => 0,
370
            'limit' => 1,
371
            'timeout' => 0,
372
        ];
373
374
        // Start the list
375
        $updates_string = '[';
376
377
        // Flag to skip adding ", " to the string
378
        $first_string = true;
379
380
        // Iterate over the list
381
        foreach ($allowed_updates as $index => $update) {
382
383
            // Is it the first update added?
384
            if (!$first_string) {
385
386
                $updates_string .= ', "' . $update . '"';
387
388
            } else {
389
390
                $updates_string .= '"' . $update . '"';
391
392
                // Set the flag to false cause we added an item
393
                $first_string = false;
394
395
            }
396
397
        }
398
399
        // Close string with the marker
400
        $updates_string .= ']';
401
402
        // Exec getUpdates
403
        $this->exec_curl_request($this->api_url . 'getUpdates?' . http_build_query($parameters) . '&allowed_updates=' . $updates_string);
404
405
    }
406
407
    /**
408
     * \brief Send a text message.
409
     * \details Use this method to send text messages. [Api reference](https://core.telegram.org/bots/api#sendmessage)
410
     * @param $text Text of the message.
411
     * @param $reply_markup <i>Optional</i>. Reply_markup of the message.
412
     * @param $parse_mode <i>Optional</i>. Parse mode of the message.
413
     * @param $disable_web_preview <i>Optional</i>. Disables link previews for links in this message.
414
     * @param $disable_notification <i>Optional</i>. Sends the message silently.
415
     * @return On success,  the sent message.
416
     */
417
    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) {
418
419
        $parameters = [
420
            'chat_id' => $this->chat_id,
421
            'text' => $text,
422
            'parse_mode' => $parse_mode,
423
            'disable_web_page_preview' => $disable_web_preview,
424
            'reply_markup' => $reply_markup,
425
            'reply_to_message_id' => $reply_to,
426
            'disable_notification' => $disable_notification
427
        ];
428
429
        return $this->exec_curl_request($this->api_url . 'sendMessage?' . http_build_query($parameters));
430
431
    }
432
433
    /**
434
     * \brief Forward a message.
435
     * \details Use this method to forward messages of any kind. [Api reference](https://core.telegram.org/bots/api#forwardmessage)
436
     * @param $from_chat_id The chat where the original message was sent.
437
     * @param $message_id Message identifier (id).
438
     * @param $disable_notification <i>Optional</i>. Sends the message silently.
439
     * @return On success,  the sent message.
440
     */
441
    public function forwardMessage($from_chat_id, int $message_id, bool $disable_notification = false) {
442
443
        $parameters = [
444
            'chat_id' => $this->chat_id,
445
            'message_id' => $message_id,
446
            'from_chat_id' => $from_chat_id,
447
            'disable_notification' => $disable_notification
448
        ];
449
450
        return $this->exec_curl_request($this->api_url . 'forwardMessage?' . http_build_query($parameters));
451
452
    }
453
454
    /**
455
     * \brief Send a photo.
456
     * \details Use this method to send photos. [Api reference](https://core.telegram.org/bots/api#sendphoto)
457
     * @param $photo Photo to send, can be a file_id or a string referencing the location of that image.
458
     * @param $reply_markup <i>Optional</i>. Reply markup of the message.
459
     * @param $caption <i>Optional</i>. Photo caption (may also be used when resending photos by file_id), 0-200 characters.
460
     * @param $disable_notification <i>Optional<i>. Sends the message silently.
461
     * @return On success,  the sent message.
462
     */
463
    public function sendPhoto($photo, string $reply_markup = null, string $caption = '', bool $disable_notification = false) {
464
465
        $parameters = [
466
            'chat_id' => $this->chat_id,
467
            'photo' => $photo,
468
            'caption' => $caption,
469
            'reply_markup' => $reply_markup,
470
            'disable_notification' => $disable_notification,
471
        ];
472
473
        return $this->exec_curl_request($this->api_url . 'sendPhoto?' . http_build_query($parameters));
474
475
    }
476
477
    /**
478
     * \brief Send an audio.
479
     * \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)
480
     * Bots can currently send audio files of up to 50 MB in size, this limit may be changed in the future.
481
     * @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.
482
     * @param $caption <i>Optional</i>. Audio caption, 0-200 characters.
483
     * @param $reply_markup <i>Optional</i>. Reply markup of the message.
484
     * @param $duration <i>Optional</i>. Duration of the audio in seconds.
485
     * @param $performer <i>Optional</i>. Performer.
486
     * @param $title <i>Optional</i>. Track name.
487
     * @param $disable_notification <i>Optional</i>. Sends the message silently.
488
     * @param $reply_to_message_id <i>Optional</i>. If the message is a reply, ID of the original message.
489
     * @return On success, the sent message.
490
     */
491
    public function sendAudio($audio, string $caption = null, string $reply_markup = null, int $duration = null, string $title = null, bool $disable_notification = false, int $reply_to_message_id = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $audio is not used and could be removed.

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

Loading history...
492
493
        $parameters = [
494
            'chat_id' => $this->chat_id,
495
            'audio' => $photo,
0 ignored issues
show
Bug introduced by
The variable $photo does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
496
            'caption' => $caption,
497
            'duration' => $duration,
498
            'performer' => $performer,
0 ignored issues
show
Bug introduced by
The variable $performer does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
499
            'title' => $title,
500
            'reply_to_message_id' => $reply_to_message_id,
501
            'reply_markup' => $reply_markup,
502
            'disable_notification' => $disable_notification,
503
        ];
504
505
        return $this->exec_curl_request($this->api_url . 'sendAudio?' . http_build_query($parameters));
506
507
    }
508
509
    /**
510
     * \brief Send a document.
511
     * \details Use this method to send general files. [Api reference](https://core.telegram.org/bots/api/#senddocument)
512
     * @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.
513
     * @param <i>Optional</i>. Document caption (may also be used when resending documents by file_id), 0-200 characters.
514
     *
515
     * @param $reply_markup <i>Optional</i>. Reply markup of the message.
516
     * @param <i>Optional</i>. Sends the message silently.
517
     * @param <i>Optional</i>. If the message is a reply, ID of the original message.
518
     */
519
    public function sendDocument($document, string $caption = '', string $reply_markup = null, bool $disable_notification = false, int $reply_to_message_id = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $document is not used and could be removed.

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

Loading history...
520
521
        $parameters = [
522
            'chat_id' => $this->chat_id,
523
            'document' => $photo,
0 ignored issues
show
Bug introduced by
The variable $photo does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
524
            'caption' => $caption,
525
            'reply_to_message_id' => $reply_to_message_id,
526
            'reply_markup' => $reply_markup,
527
            'disable_notification' => $disable_notification,
528
        ];
529
530
        return $this->exec_curl_request($this->api_url . 'sendAudio?' . http_build_query($parameters));
531
532
    }
533
534
535
    /**
536
     * \brief Send a sticker
537
     * \details Use this method to send .webp stickers. [Api reference](https://core.telegram.org/bots/api/#sendsticker)
538
     * @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.
539
     * @param $reply_markup <i>Optional</i>. Reply markup of the message.
540
     * @param $disable_notification Sends the message silently.
541
     * @param <i>Optional</i>. If the message is a reply, ID of the original message.
542
     * @param On success, the sent message.
543
     */
544
    public function sendSticker($sticker, string $reply_markup = null, bool $disable_notification = false, int $reply_to_message_id = null) {
545
546
        $parameters = [
547
            'chat_id' => $this->chat_id,
548
            'sticker' => $sticker,
549
            'disable_notification' => $disable_notification,
550
            'reply_to_message_id' => $reply_to_message_id,
551
            'reply_markup' => $reply_markup
552
        ];
553
554
        return $this->exec_curl_request($this->api_url . 'sendSticker?' . http_build_query($parameters));
555
556
    }
557
558
    /**
559
     * \brief Send audio files.
560
     * \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
561
     * Bots can currently send voice messages of up to 50 MB in size, this limit may be changed in the future.
562
     * @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.
563
     * @param $caption <i>Optional</i>. Voice message caption, 0-200 characters
564
     * @param $duration <i>Optional</i>. Duration of the voice message in seconds
565
     * @param $reply_markup <i>Optional</i>. Reply markup of the message.
566
     * @param $disable_notification <i>Optional</i>. Sends the message silently.
567
     * @param $reply_to_message_id <i>Optional</i>. If the message is a reply, ID of the original message.
568
     * @return On success, the sent message is returned.
569
     */
570 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...
571
572
        $parameters = [
573
            'chat_id' => $this->chat_id,
574
            'voice' => $voice,
575
            'caption' => $caption,
576
            'duration' => $duration,
577
            'disable_notification', $disable_notification,
578
            'reply_to_message_id' => $reply_to_message_id,
579
            'reply_markup' => $reply_markup
580
        ];
581
582
        return $this->exec_curl_request($this->api_url . 'sendVoice?' . http_build_query($parameters));
583
584
    }
585
586
    /**
587
     * \brief Say the user what action is the bot doing.
588
     * \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)
589
     * @param $action Type of action to broadcast. Choose one, depending on what the user is about to receive:
590
     * - <code>typing</code> for text messages
591
     * - <code>upload_photo</code> for photos
592
     * - <code>record_video</code> or <code>upload_video</code> for videos
593
     * - <code>record_audio</code> or <code>upload_audio</code> for audio files
594
     * - <code>upload_document</code> for general files
595
     * - <code>find_location</code> for location data
596
     * @return True on success.
597
     */
598 View Code Duplication
    public function sendChatAction(string $action) : bool {
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...
599
600
        $parameters = [
601
            'chat_id' => $this->chat_id,
602
            'action' => $action
603
        ];
604
605
        return $this->exec_curl_request($this->api_url . 'sendChatAction?' . http_build_query($parameters));
606
607
    }
608
609
    /**
610
     * \brief Get info about a chat.
611
     * \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)
612
     * @param Unique identifier for the target chat or username of the target supergroup or channel (in the format <code>@channelusername</code>)
613
     */
614 View Code Duplication
    public function getChat($chat_id) {
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...
615
616
        $parameters = [
617
            'chat_id' => $chat_id,
618
        ];
619
620
        return $this->exec_curl_request($this->api_url . 'getChat?' . http_build_query($parameters));
621
622
    }
623
624
    /* \brief Answer a callback query
625
     * \details Remove the updating cirle on an inline keyboard button and showing a message/alert to the user.
626
     * It will always answer the current callback query.
627
     * @param $text <i>Optional</i>. Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters.
628
     * @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.
629
     * @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.
630
     * Otherwise, you may use links like telegram.me/your_bot?start=XXXX that open your bot with a parameter.
631
     * @return True on success.
632
     */
633
    public function answerCallbackQuery($text = '', $show_alert = false, string $url = '') : bool {
634
635
        $parameters = [
636
            'callback_query_id' => $this->update['callback_query']['id'],
0 ignored issues
show
Bug introduced by
The property update does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
637
            'text' => $text,
638
            'show_alert' => $show_alert,
639
            'url' => $url
640
        ];
641
642
        return $this->exec_curl_request($this->api_url . 'answerCallbackQuery?' . http_build_query($parameters));
643
644
    }
645
646
    /**
647
     * \brief Edit text of a message sent by the bot.
648
     * \details Use this method to edit text and game messages sent by the bot. [Api reference](https://core.telegram.org/bots/api#editmessagetext)
649
     * @param $message_id Unique identifier of the sent message.
650
     * @param $text New text of the message.
651
     * @param $reply_markup Reply markup of the message will have (will be removed if this is null).
652
     * @param $parse_mode <i>Optional</i>. Send Markdown or HTML.
653
     * @param $disable_web_preview <i>Optional</i>. Disables link previews for links in this message.
654
     */
655 View Code Duplication
    public function editMessageText(int $message_id, $text, $reply_markup = null, string $parse_mode = 'HTML', bool $disable_web_preview = false) {
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...
656
657
        $parameters = [
658
            'chat_id' => $this->chat_id,
659
            'message_id' => $message_id,
660
            'text' => $text,
661
            'reply_markup' => $reply_markup,
662
            'parse_mode' => $parse_mode,
663
            'disable_web_page_preview' => $disable_web_preview,
664
        ];
665
666
        return $this->exec_curl_request($this->api_url . 'editMessageText?' . http_build_query($parameters));
667
668
    }
669
670
    /**
671
     * \brief Edit text of a message sent via the bot.
672
     * \details Use this method to edit text messages sent via the bot (for inline queries). [Api reference](https://core.telegram.org/bots/api#editmessagetext)
673
     * @param $inline_message_id  Identifier of the inline message.
674
     * @param $text New text of the message.
675
     * @param $reply_markup Reply markup of the message will have (will be removed if this is null).
676
     * @param $parse_mode <i>Optional</i>. Send Markdown or HTML.
677
     * @param $disable_web_preview <i>Optional</i>. Disables link previews for links in this message.
678
     */
679
    public function editInlineMessageText(string $inline_message_id, $text, string $reply_markup = null, string $parse_mode = 'HTML', bool $disable_web_preview = false) {
0 ignored issues
show
Unused Code introduced by
The parameter $reply_markup is not used and could be removed.

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

Loading history...
680
681
        $parameters = [
682
           'inline_message_id' => $inline_message_id,
683
           'text' => $text,
684
           'reply_markup' => $inline_keyboard,
0 ignored issues
show
Bug introduced by
The variable $inline_keyboard does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
685
           'parse_mode' => $parse_mode,
686
           'disable_web_page_preview' => $disable_web_preview,
687
        ];
688
689
        return $this->exec_curl_request($this->api_url . 'editMessageText?' . http_build_query($parameters));
690
691
    }
692
693
    /*
694
     * Edit only the inline keyboard of a message (https://core.telegram.org/bots/api#editmessagereplymarkup)ù
695
     * @param
696
     * $message_id Identifier of the message to edit
697
     * $inline_keyboard Inlike keyboard array (https://core.telegram.org/bots/api#inlinekeyboardmarkup)
698
     */
699 View Code Duplication
    public function editMessageReplyMarkup($message_id, $inline_keyboard) {
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...
700
701
        $parameters = [
702
            'chat_id' => $this->chat_id,
703
            'message_id' => $message_id,
704
            'reply_markup' => $inline_keyboard,
705
        ];
706
707
        return $this->exec_curl_request($this->api_url . 'editMessageReplyMarkup?' . http_build_query($parameters));
708
709
    }
710
711
    /*
712
     * 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)
713
     * @param
714
     * $results Array on InlineQueryResult (https://core.telegram.org/bots/api#inlinequeryresult)
715
     * $switch_pm_text Text to show on the button
716
     */
717 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...
718
719
        $parameters = [
720
            'inline_query_id' => $this->update['inline_query']['id'],
721
            'switch_pm_text' => $switch_pm_text,
722
            'is_personal' => $is_personal,
723
            'switch_pm_parameter' => $switch_pm_parameter,
724
            'results' => $results,
725
            'cache_time' => $cache_time
726
        ];
727
728
        return $this->exec_curl_request($this->api_url . 'answerInlineQuery?' . http_build_query($parameters));
729
730
    }
731
732
    /*
733
     * 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)
734
     * without showing any results to the user
735
     * @param
736
     * $switch_pm_text Text to show on the button
737
     */
738 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...
739
        $parameters = [
740
            'inline_query_id' => $this->update['inline_query']['id'],
741
            'switch_pm_text' => $switch_pm_text,
742
            'is_personal' => $is_personal,
743
            'switch_pm_parameter' => $switch_pm_parameter,
744
            'cache_time' => $cache_time
745
        ];
746
747
        return $this->exec_curl_request($this->api_url . 'answerInlineQuery?' . http_build_query($parameters));
748
749
    }
750
751
    /**
752
     * \brief Exec any api request using this method.
753
     * \details Use this method for custom api calls using this syntax:
754
     *
755
     *     $param = [
756
     *             'chat_id' => $chat_id,
757
     *             'text' => 'Hello!'
758
     *     ];
759
     *     apiRequest("sendMessage", $param);
760
     *
761
     * @param $method The method to call.
762
     * @param $parameters Parameters to add.
763
     * @return Depends on api method.
764
     */
765
    public function apiRequest(string $method, array $parameters) {
766
767
        return $this->exec_curl_request($this->api_url . $method . '?' . http_build_query($parameters));
768
769
    }
770
771
    /** @} */
772
773
    /**
774
     * \addtogroup Core Core(internal)
775
     * @{
776
     */
777
778
    /** \brief Core function to execute url request.
779
     * @param $url The url to call using the curl session.
780
     * @return Url response, false on error.
781
     */
782
    protected function exec_curl_request($url) {
783
784
        // Set the url
785
        curl_setopt($this->ch, CURLOPT_URL, $url);
786
787
        $response = curl_exec($this->ch);
788
789
        if ($response === false) {
790
            $errno = curl_errno($this->ch);
791
            $error = curl_error($this->ch);
792
            error_log("Curl returned error $errno: $error\n");
793
            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 DanySpin97\PhpBotFramewo...eBot::exec_curl_request of type DanySpin97\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...
794
        }
795
796
        $http_code = intval(curl_getinfo($this->ch, CURLINFO_HTTP_CODE));
797
798
        if ($http_code === 200) {
799
            $response = json_decode($response, true);
800
            if (isset($response['desc'])) {
801
                error_log("Request was successfull: {$response['description']}\n");
802
            }
803
            return $response['result'];
804
        } elseif ($http_code >= 500) {
805
            // do not wat to DDOS server if something goes wrong
806
            sleep(10);
807
            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 DanySpin97\PhpBotFramewo...eBot::exec_curl_request of type DanySpin97\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...
808
        } elseif ($http_code !== 200) {
809
            $response = json_decode($response, true);
810
            error_log("Request has failed with error {$response['error_code']}: {$response['description']}\n");
811
            if ($http_code === 401) {
812
                throw new BotException('Invalid access token provided');
813
            }
814
            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 DanySpin97\PhpBotFramewo...eBot::exec_curl_request of type DanySpin97\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...
815
        }
816
817
        return $response;
818
    }
819
820
    /** @} */
821
822
}
823
824