Passed
Push — master ( 6a6913...9c7b82 )
by Shahrad
02:17
created

Telegram::setDebugMode()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 5
nc 6
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace TelegramBot;
4
5
use TelegramBot\Entities\Response;
6
use TelegramBot\Entities\Update;
7
use TelegramBot\Exception\TelegramException;
8
use TelegramBot\Util\Common;
9
use TelegramBot\Util\DotEnv;
10
11
/**
12
 * Class Telegram
13
 *
14
 * @link    https://github.com/telegram-bot-php/core
15
 * @author  Shahrad Elahi (https://github.com/shahradelahi)
16
 * @license https://github.com/telegram-bot-php/core/blob/master/LICENSE (MIT License)
17
 */
18
class Telegram
19
{
20
21
    /**
22
     * @var string
23
     */
24
    private string $api_key;
25
26
    /**
27
     * @var string
28
     */
29
    public static string $VERSION = 'v1.0.0';
30
31
    /**
32
     * Telegram constructor.
33
     *
34
     * @param string $api_key
35
     */
36
    public function __construct(string $api_key = '')
37
    {
38
        if ($api_key === '') {
39
            $env_file = $this->getEnvFilePath();
40
            $api_key = DotEnv::load($env_file)->get('TELEGRAM_API_KEY');
41
        }
42
43
        if (empty($api_key) || !is_string($api_key)) {
44
            throw new TelegramException('API Key is required');
45
        }
46
47
        DotEnv::put('TG_CURRENT_KEY', ($this->api_key = $api_key));
48
        DotEnv::put('TELEGRAM_API_KEY', ($this->api_key = $api_key));
49
    }
50
51
    /**
52
     * Get env file path and return it
53
     *
54
     * @return string
55
     */
56
    private function getEnvFilePath(): string
57
    {
58
        $defaultEnvPaths = [
59
            $_SERVER['DOCUMENT_ROOT'] . '/.env',
60
            getcwd() . '/../.env',
61
            getcwd() . '/.env',
62
        ];
63
64
        foreach ($defaultEnvPaths as $path) {
65
            if (file_exists($path)) {
66
                return $path;
67
            }
68
        }
69
70
        return '';
71
    }
72
73
    /**
74
     * Get API key from temporary ENV variable
75
     *
76
     * @return ?string
77
     */
78
    public static function getApiKey(): ?string
79
    {
80
        return DotEnv::get('TG_CURRENT_KEY');
81
    }
82
83
    /**
84
     * Get bot info from given API key
85
     *
86
     * @return Response
87
     * @throws TelegramException
88
     */
89
    public function getInfo(): Response
90
    {
91
        $result = Request::getMe();
92
93
        if (!$result->isOk()) {
94
            throw new TelegramException($result->getErrorCode() . ': ' . $result->getDescription());
95
        }
96
97
        return $result;
98
    }
99
100
    /**
101
     * Set Webhook for bot
102
     *
103
     * @param string $url
104
     * @param array $data Optional parameters.
105
     * @return Response
106
     * @throws TelegramException
107
     */
108
    public function setWebhook(string $url, array $data = []): Response
109
    {
110
        if ($url === '') {
111
            throw new TelegramException('Hook url is empty!');
112
        }
113
114
        if (!str_starts_with($url, 'https://')) {
115
            throw new TelegramException('Hook url must start with https://');
116
        }
117
118
        $data = array_intersect_key($data, array_flip([
119
            'certificate',
120
            'ip_address',
121
            'max_connections',
122
            'allowed_updates',
123
            'drop_pending_updates',
124
        ]));
125
        $data['url'] = $url;
126
127
        $result = Request::setWebhook($data);
128
129
        if (!$result->isOk()) {
130
            throw new TelegramException(
131
                'Webhook was not set! Error: ' . $result->getErrorCode() . ' ' . $result->getDescription()
132
            );
133
        }
134
135
        return $result;
136
    }
137
138
    /**
139
     * Delete any assigned webhook
140
     *
141
     * @param array $data
142
     * @return Response
143
     * @throws TelegramException
144
     */
145
    public function deleteWebhook(array $data = []): Response
146
    {
147
        $result = Request::deleteWebhook($data);
148
149
        if (!$result->isOk()) {
150
            throw new TelegramException(
151
                'Webhook was not deleted! Error: ' . $result->getErrorCode() . ' ' . $result->getDescription()
152
            );
153
        }
154
155
        return $result;
156
    }
157
158
    /**
159
     * This method sets the admin username. and will be used to send you a message if the bot is not working.
160
     *
161
     * @param int $chat_id
162
     * @return void
163
     */
164
    public function setAdmin(int $chat_id): void
165
    {
166
        defined('TG_ADMIN_ID') or define('TG_ADMIN_ID', $chat_id);
167
    }
168
169
    /**
170
     * Get input from stdin and return it
171
     *
172
     * @return ?string
173
     */
174
    public static function getInput(): ?string
175
    {
176
        return file_get_contents('php://input') ?? null;
177
    }
178
179
    /**
180
     * This method will convert a string to an update object
181
     *
182
     * @param string $input The input string
183
     * @param string $apiKey The API key
184
     * @return Update|false
185
     */
186
    public static function processUpdate(string $input, string $apiKey): Update|false
187
    {
188
        if (empty($input)) {
189
            throw new TelegramException(
190
                'Input is empty! Please check your code and try again.'
191
            );
192
        }
193
194
        if (!self::validateToken($apiKey)){
195
            throw new TelegramException(
196
                'Invalid token! Please check your code and try again.'
197
            );
198
        }
199
200
        if (self::validateWebData($apiKey, $input)) {
201
            if (Common::isUrlEncode($input)) {
202
                $web_data = Common::urlDecode($input);
203
            }
204
205
            if (Common::isJson($input)) {
206
                $web_data = json_decode($input, true);
207
            }
208
209
            $input = json_encode([
210
                'web_data' => $web_data,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $web_data does not seem to be defined for all execution paths leading up to this point.
Loading history...
211
            ]);
212
        }
213
214
        if (!Common::isJson($input)) {
215
            throw new TelegramException(
216
                'Input is not a valid JSON string! Please check your code and try again.'
217
            );
218
        }
219
220
        $input = json_decode($input, true);
221
222
        return new Update($input);
223
    }
224
225
    /**
226
     * Validate webapp data from is from Telegram
227
     *
228
     * @link https://core.telegram.org/bots/webapps#validating-data-received-via-the-web-app
229
     *
230
     * @param string $token The bot token
231
     * @param string $body The message body from getInput()
232
     * @return bool
233
     */
234
    public static function validateWebData(string $token, string $body): bool
235
    {
236
        if (!Common::isJson($body)) {
237
            $raw_data = rawurldecode(str_replace('_auth=', '', $body));
238
            $data = Common::urlDecode($raw_data);
239
240
            $data['user'] = urldecode($data['user']);
241
242
        } else {
243
            $data = json_decode($body, true);
244
            $data['user'] = json_encode($data['user']);
245
        }
246
247
        $data_check_string = "auth_date={$data['auth_date']}\nquery_id={$data['query_id']}\nuser={$data['user']}";
248
        $secret_key = hash_hmac('sha256', $token, "WebAppData", true);
249
250
        return hash_hmac('sha256', $data_check_string, $secret_key) == $data['hash'];
251
    }
252
253
    /**
254
     * Get the update from input
255
     *
256
     * @return Update|false
257
     */
258
    public static function getUpdate(): Update|false
259
    {
260
        $input = self::getInput();
261
        if (empty($input)) return false;
262
        return Telegram::processUpdate($input, self::getApiKey());
263
    }
264
265
    /**
266
     * Validate the token
267
     *
268
     * @param string $token (e.g. 123456789:ABC-DEF1234ghIkl-zyx57W2v1u123ew11) {digit}:{alphanumeric[34]}
269
     * @return bool
270
     */
271
    public static function validateToken(string $token): bool
272
    {
273
        preg_match_all('/([0-9]+:[a-zA-Z0-9-_]+)/', $token, $matches);
274
        return count($matches[0]) == 1;
275
    }
276
277
    /**
278
     * Pass the update to the given webhook handler
279
     *
280
     * @param WebhookHandler $webhook_handler The webhook handler
281
     * @param ?Update $update By default, it will get the update from input
282
     * @return void
283
     */
284
    public function fetchWith(WebhookHandler $webhook_handler, ?Update $update = null): void
285
    {
286
        if (is_subclass_of($webhook_handler, WebhookHandler::class)) {
287
            if ($update === null) $update = self::getUpdate();
288
            $webhook_handler->resolve($update);
0 ignored issues
show
Bug introduced by
It seems like $update can also be of type false; however, parameter $update of TelegramBot\WebhookHandler::resolve() does only seem to accept TelegramBot\Entities\Update|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

288
            $webhook_handler->resolve(/** @scrutinizer ignore-type */ $update);
Loading history...
289
        }
290
    }
291
292
    /**
293
     * Get token from env file.
294
     *
295
     * @param string $file
296
     * @return ?string
297
     */
298
    protected function getTokenFromEnvFile(string $file): ?string
299
    {
300
        if (!file_exists($file)) return null;
301
        return DotEnv::load($file)::get('TELEGRAM_API_KEY');
302
    }
303
304
    /**
305
     * Debug mode
306
     *
307
     * @param ?int $admin_id Fill this or use setAdmin()
308
     * @return void
309
     */
310
    public static function setDebugMode(?int $admin_id = null): void
311
    {
312
        error_reporting(E_ALL);
313
        ini_set('display_errors', 1);
314
        defined('DEBUG_MODE') or define('DEBUG_MODE', true);
315
        if ($admin_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $admin_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
316
            defined('TG_ADMIN_ID') or define('TG_ADMIN_ID', $admin_id);
317
        }
318
    }
319
320
    /**
321
     * Just another echo
322
     *
323
     * @param string $text
324
     * @return void
325
     */
326
    public static function echo(string $text): void
327
    {
328
        echo $text;
329
    }
330
331
}