Passed
Push — master ( a0ae72...f853f9 )
by Shahrad
02:06
created

Telegram::processUpdate()   B

Complexity

Conditions 7
Paths 21

Size

Total Lines 35
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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

247
        return Telegram::processUpdate(/** @scrutinizer ignore-type */ $input);
Loading history...
248
    }
249
250
    /**
251
     * Validate the token
252
     *
253
     * @param string $token (e.g. 123456789:ABC-DEF1234ghIkl-zyx57W2v1u123ew11) {digit}:{alphanumeric[34]}
254
     * @return bool
255
     */
256
    public static function validateToken(string $token): bool
257
    {
258
        preg_match_all('/([0-9]+:[a-zA-Z0-9-_]+)/', $token, $matches);
259
        return count($matches[0]) == 1;
260
    }
261
262
    /**
263
     * Pass the update to the given webhook handler
264
     *
265
     * @param WebhookHandler $webhook_handler The webhook handler
266
     * @param ?Update $update By default, it will get the update from input
267
     * @return void
268
     */
269
    public function fetchWith(WebhookHandler $webhook_handler, ?Update $update = null): void
270
    {
271
        if (is_subclass_of($webhook_handler, WebhookHandler::class)) {
272
            if ($update === null) $update = self::getUpdate();
273
            $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

273
            $webhook_handler->resolve(/** @scrutinizer ignore-type */ $update);
Loading history...
274
        }
275
    }
276
277
    /**
278
     * Get token from env file.
279
     *
280
     * @param string $file
281
     * @return ?string
282
     */
283
    protected function getTokenFromEnvFile(string $file): ?string
284
    {
285
        if (!file_exists($file)) return null;
286
        return DotEnv::load($file)::get('TELEGRAM_API_KEY');
287
    }
288
289
    /**
290
     * Just another echo
291
     *
292
     * @param string $text
293
     * @return void
294
     */
295
    public static function echo(string $text): void
296
    {
297
        echo $text;
298
    }
299
300
}