Passed
Push — master ( 4c2b59...061776 )
by Shahrad
02:09
created

Telegram::getInfo()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
dl 0
loc 9
c 1
b 0
f 0
rs 10
cc 2
nc 2
nop 0
1
<?php
2
3
namespace TelegramBot;
4
5
use Symfony\Component\Dotenv\Dotenv;
6
use TelegramBot\Entities\Response;
7
use TelegramBot\Entities\Update;
8
use TelegramBot\Exception\TelegramException;
9
use TelegramBot\Util\Toolkit;
10
11
/**
12
 * Telegram class
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
    public static string $VERSION = 'v1.0.0';
25
26
    /**
27
     * @var string|null
28
     */
29
    private static string|null $api_key = null;
30
31
    /**
32
     * Telegram constructor.
33
     *
34
     * @param string $api_token
35
     */
36
    public function __construct(string $api_token = '')
37
    {
38
        if ($api_token === '') {
39
            (new Dotenv())->load($this->getEnvFilePath());
40
            $api_token = $_ENV['TELEGRAM_BOT_TOKEN'];
41
        }
42
43
        if (empty($api_token) || self::validateToken($api_token) === false) {
44
            throw new TelegramException(sprintf(
45
                'Invalid Telegram API token: %s',
46
                $api_token
47
            ));
48
        }
49
50
        self::setToken($api_token);
51
    }
52
53
    /**
54
     * Set the current bot token.
55
     *
56
     * @param string $api_key
57
     * @return void
58
     */
59
    public static function setToken(string $api_key): void
60
    {
61
        static::$api_key = $api_key;
0 ignored issues
show
Bug introduced by
Since $api_key is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $api_key to at least protected.
Loading history...
62
        $_ENV['TELEGRAM_BOT_TOKEN'] = $api_key;
63
    }
64
65
    /**
66
     * Get the current bot token.
67
     *
68
     * @return string|false
69
     */
70
    public static function getApiToken(): string|false
71
    {
72
        return self::$api_key !== null ? (self::validateToken(self::$api_key) ? self::$api_key : false) : false;
73
    }
74
75
    /**
76
     * Get env file path and return it
77
     *
78
     * @return string
79
     */
80
    private function getEnvFilePath(): string
81
    {
82
        $defaultEnvPaths = [
83
            $_SERVER['DOCUMENT_ROOT'] . '/.env',
84
            getcwd() . '/../.env',
85
            getcwd() . '/.env',
86
        ];
87
88
        foreach ($defaultEnvPaths as $path) {
89
            if (file_exists($path)) {
90
                return $path;
91
            }
92
        }
93
94
        return '';
95
    }
96
97
    /**
98
     * Get token from env file.
99
     *
100
     * @param string $file
101
     * @return string|null
102
     */
103
    protected function getEnvToken(string $file): string|null
104
    {
105
        if (!file_exists($file)) return null;
106
        return $_ENV['TELEGRAM_BOT_TOKEN'] ?? null;
107
    }
108
109
    /**
110
     * Get bot info from given API key
111
     *
112
     * @return Response
113
     * @throws TelegramException
114
     */
115
    public function getInfo(): Response
116
    {
117
        $result = Request::getMe();
118
119
        if (!$result->isOk()) {
120
            throw new TelegramException($result->getErrorCode() . ': ' . $result->getDescription());
121
        }
122
123
        return $result;
124
    }
125
126
    /**
127
     * Set Webhook for bot
128
     *
129
     * @param string $url
130
     * @param array $data Optional parameters.
131
     * @return Response
132
     * @throws TelegramException
133
     */
134
    public function setWebhook(string $url, array $data = []): Response
135
    {
136
        if ($url === '') {
137
            throw new TelegramException('Hook url is empty!');
138
        }
139
140
        if (!str_starts_with($url, 'https://')) {
141
            throw new TelegramException('Hook url must start with https://');
142
        }
143
144
        $data = array_intersect_key($data, array_flip([
145
            'certificate',
146
            'ip_address',
147
            'max_connections',
148
            'allowed_updates',
149
            'drop_pending_updates',
150
        ]));
151
        $data['url'] = $url;
152
153
        $result = Request::setWebhook($data);
154
155
        if (!$result->isOk()) {
156
            throw new TelegramException(
157
                'Webhook was not set! Error: ' . $result->getErrorCode() . ' ' . $result->getDescription()
158
            );
159
        }
160
161
        return $result;
162
    }
163
164
    /**
165
     * Delete any assigned webhook
166
     *
167
     * @param array $data
168
     * @return Response
169
     * @throws TelegramException
170
     */
171
    public function deleteWebhook(array $data = []): Response
172
    {
173
        $result = Request::deleteWebhook($data);
174
175
        if (!$result->isOk()) {
176
            throw new TelegramException(
177
                'Webhook was not deleted! Error: ' . $result->getErrorCode() . ' ' . $result->getDescription()
178
            );
179
        }
180
181
        return $result;
182
    }
183
184
    /**
185
     * Pass the update to the given webhook handler
186
     *
187
     * @param UpdateHandler $webhook_handler The webhook handler
188
     * @param Update|null $update By default, it will get the update from input
189
     * @return void
190
     */
191
    public function fetchWith(UpdateHandler $webhook_handler, Update|null $update = null): void
192
    {
193
        if (is_subclass_of($webhook_handler, UpdateHandler::class)) {
194
            if ($update === null) $update = self::getUpdate();
195
            $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\UpdateHandler::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

195
            $webhook_handler->resolve(/** @scrutinizer ignore-type */ $update);
Loading history...
196
        }
197
    }
198
199
    /**
200
     * Get the update from input
201
     *
202
     * @return Update|false
203
     */
204
    public static function getUpdate(): Update|false
205
    {
206
        $input = self::getInput();
207
        if (empty($input)) return false;
208
        return Telegram::processUpdate($input, self::getApiToken());
0 ignored issues
show
Bug introduced by
It seems like self::getApiToken() can also be of type false; however, parameter $apiKey of TelegramBot\Telegram::processUpdate() does only seem to accept null|string, 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

208
        return Telegram::processUpdate($input, /** @scrutinizer ignore-type */ self::getApiToken());
Loading history...
209
    }
210
211
    /**
212
     * Get input from stdin and return it
213
     *
214
     * @return string|null
215
     */
216
    public static function getInput(): string|null
217
    {
218
        return file_get_contents('php://input') ?? null;
219
    }
220
221
    /**
222
     * This method will convert a string to an update object
223
     *
224
     * @param string $input The input string
225
     * @param string|null $apiKey The API key
226
     * @return Update|false
227
     */
228
    public static function processUpdate(string $input, string|null $apiKey = null): Update|false
229
    {
230
        if (empty($input)) {
231
            throw new TelegramException(
232
                'Input is empty! Please check your code and try again.'
233
            );
234
        }
235
236
        if ($apiKey !== null && !self::validateToken($apiKey)) {
237
            throw new TelegramException(
238
                'Invalid token! Please check your code and try again.'
239
            );
240
        }
241
242
        if ($apiKey !== null && self::validateWebData($apiKey, $input)) {
243
            if (Toolkit::isUrlEncode($input)) {
244
                $web_data = Toolkit::urlDecode($input);
245
            }
246
247
            if (Toolkit::isJson($input)) {
248
                $web_data = json_decode($input, true);
249
            }
250
251
            if (!empty($web_data) && is_array($web_data)) {
252
                $input = json_encode([
253
                    'web_data' => $web_data,
254
                ]);
255
            }
256
        }
257
258
        if (!Toolkit::isJson($input)) {
259
            throw new TelegramException(sprintf(
260
                "Input is not a valid JSON string! Please check your code and try again.\nInput: %s",
261
                $input
262
            ));
263
        }
264
265
        $input = json_decode($input, true);
266
267
        return new Update($input);
268
    }
269
270
    /**
271
     * Validate the token
272
     *
273
     * @param string $token (e.g. 123456789:ABC-DEF1234ghIkl-zyx57W2v1u123ew11) {digit}:{alphanumeric[34]}
274
     * @return bool
275
     */
276
    public static function validateToken(string $token): bool
277
    {
278
        preg_match_all('/([0-9]+:[a-zA-Z0-9-_]+)/', $token, $matches);
279
        return count($matches[0]) == 1;
280
    }
281
282
    /**
283
     * Validate webapp data from is from Telegram
284
     *
285
     * @link https://core.telegram.org/bots/webapps#validating-data-received-via-the-web-app
286
     *
287
     * @param string $token The bot token
288
     * @param string $body The message body from getInput()
289
     * @return bool
290
     */
291
    public static function validateWebData(string $token, string $body): bool
292
    {
293
        if (!Toolkit::isJson($body)) {
294
            $raw_data = rawurldecode(str_replace('_auth=', '', $body));
295
            $data = Toolkit::urlDecode($raw_data);
296
297
            if (empty($data['user'])) {
298
                return false;
299
            }
300
301
            $data['user'] = urldecode($data['user']);
302
303
        } else {
304
            $data = json_decode($body, true);
305
306
            if (empty($data['user'])) {
307
                return false;
308
            }
309
310
            $data['user'] = json_encode($data['user']);
311
        }
312
313
        $data_check_string = "auth_date={$data['auth_date']}\nquery_id={$data['query_id']}\nuser={$data['user']}";
314
        $secret_key = hash_hmac('sha256', $token, "WebAppData", true);
315
316
        return hash_hmac('sha256', $data_check_string, $secret_key) == $data['hash'];
317
    }
318
319
}