Passed
Push — master ( e20c00...b14591 )
by Shahrad
02:03
created

Telegram::validateWebData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 13
rs 10
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
            $defaultEnvPaths = [
40
                $_SERVER['DOCUMENT_ROOT'] . '/.env',
41
                getcwd() . '/.env'
42
            ];
43
44
            foreach ($defaultEnvPaths as $path) {
45
                if (file_exists($path)) {
46
                    $api_key = DotEnv::load($path)::get('TELEGRAM_API_KEY');
47
                    break;
48
                }
49
            }
50
        }
51
52
        if (empty($api_key) || !is_string($api_key)) {
53
            throw new TelegramException('API Key is required');
54
        }
55
56
        DotEnv::put('TG_CURRENT_KEY', ($this->api_key = $api_key));
57
        DotEnv::put('TELEGRAM_API_KEY', ($this->api_key = $api_key));
58
    }
59
60
    /**
61
     * Get API key from temporary ENV variable
62
     *
63
     * @return string
64
     */
65
    public static function getApiKey(): string
66
    {
67
        return DotEnv::get('TG_CURRENT_KEY');
68
    }
69
70
    /**
71
     * Get bot info from given API key
72
     *
73
     * @return Response
74
     * @throws TelegramException
75
     */
76
    public function getInfo(): Response
77
    {
78
        $result = Request::getMe();
79
80
        if (!$result->isOk()) {
81
            throw new TelegramException($result->getErrorCode() . ': ' . $result->getDescription());
82
        }
83
84
        return $result;
85
    }
86
87
    /**
88
     * Set Webhook for bot
89
     *
90
     * @param string $url
91
     * @param array $data Optional parameters.
92
     * @return Response
93
     * @throws TelegramException
94
     */
95
    public function setWebhook(string $url, array $data = []): Response
96
    {
97
        if ($url === '') {
98
            throw new TelegramException('Hook url is empty!');
99
        }
100
101
        if (!str_starts_with($url, 'https://')) {
102
            throw new TelegramException('Hook url must start with https://');
103
        }
104
105
        $data = array_intersect_key($data, array_flip([
106
            'certificate',
107
            'ip_address',
108
            'max_connections',
109
            'allowed_updates',
110
            'drop_pending_updates',
111
        ]));
112
        $data['url'] = $url;
113
114
        $result = Request::setWebhook($data);
115
116
        if (!$result->isOk()) {
117
            throw new TelegramException(
118
                'Webhook was not set! Error: ' . $result->getErrorCode() . ' ' . $result->getDescription()
119
            );
120
        }
121
122
        return $result;
123
    }
124
125
    /**
126
     * Delete any assigned webhook
127
     *
128
     * @param array $data
129
     * @return Response
130
     * @throws TelegramException
131
     */
132
    public function deleteWebhook(array $data = []): Response
133
    {
134
        $result = Request::deleteWebhook($data);
135
136
        if (!$result->isOk()) {
137
            throw new TelegramException(
138
                'Webhook was not deleted! Error: ' . $result->getErrorCode() . ' ' . $result->getDescription()
139
            );
140
        }
141
142
        return $result;
143
    }
144
145
    /**
146
     * This method sets the admin username. and will be used to send you a message if the bot is not working.
147
     *
148
     * @param int $chat_id
149
     * @return void
150
     */
151
    public function setAdmin(int $chat_id): void
152
    {
153
        defined('TG_ADMIN_ID') or define('TG_ADMIN_ID', $chat_id);
154
    }
155
156
    /**
157
     * Get input from stdin and return it
158
     *
159
     * @return ?string
160
     */
161
    public static function getInput(): ?string
162
    {
163
        return file_get_contents('php://input') ?? null;
164
    }
165
166
    /**
167
     * This method will convert a string to an update object
168
     *
169
     * @param string $input The input string
170
     * @return Update|false
171
     */
172
    public static function processUpdate(string $input): Update|false
173
    {
174
        if ($input === '' || Common::isJson($input) === false) {
175
            throw new TelegramException('Input is empty!');
176
        }
177
178
        $update = new Update(json_decode($input, true));
179
180
        if (!$update->isOk()) return false;
181
182
        return $update;
183
    }
184
185
    /**
186
     * Validate webapp data from is from Telegram
187
     *
188
     * @link https://core.telegram.org/bots/webapps#validating-data-received-via-the-web-app
189
     *
190
     * @param string $token The bot token
191
     * @param string $body The message body from getInput()
192
     * @return bool
193
     */
194
    public static function validateWebData(string $token, string $body): bool
195
    {
196
        $raw_data = explode('&', rawurldecode($body));
197
198
        $data = [];
199
        foreach ($raw_data as $key_value_pair) {
200
            list($key, $value) = explode('=', $key_value_pair);
201
            $data[$key] = $value;
202
        }
203
204
        $data_check_string = "auth_date={$data['auth_date']}\nquery_id={$data['query_id']}\nuser={$data['user']}";
205
        $secret_key = hash_hmac('sha256', $token, "WebAppData", true);
206
        return hash_hmac('sha256', $data_check_string, $secret_key) == $data['hash'];
207
    }
208
209
    /**
210
     * Get the update from input
211
     *
212
     * @return Update|false
213
     */
214
    public static function getUpdate(): Update|false
215
    {
216
        $input = self::getInput();
217
        if ($input === '' || Common::isJson($input) === false) return false;
218
        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

218
        return Telegram::processUpdate(/** @scrutinizer ignore-type */ $input);
Loading history...
219
    }
220
221
    /**
222
     * Validate the token
223
     *
224
     * @param string $token (e.g. 123456789:ABC-DEF1234ghIkl-zyx57W2v1u123ew11) {digit}:{alphanumeric[34]}
225
     * @return bool
226
     */
227
    public static function validateToken(string $token): bool
228
    {
229
        preg_match_all('/([0-9]+:[a-zA-Z0-9-_]+)/', $token, $matches);
230
        return count($matches[0]) == 1;
231
    }
232
233
    /**
234
     * Pass the update to the given webhook handler
235
     *
236
     * @param WebhookHandler $webhook_handler The webhook handler
237
     * @param ?Update $update By default, it will get the update from input
238
     * @return void
239
     */
240
    public function fetchWith(WebhookHandler $webhook_handler, ?Update $update = null): void
241
    {
242
        if (is_subclass_of($webhook_handler, WebhookHandler::class)) {
243
            if ($update === null) $update = self::getUpdate();
244
            $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

244
            $webhook_handler->resolve(/** @scrutinizer ignore-type */ $update);
Loading history...
245
        }
246
    }
247
248
    /**
249
     * Get token from env file.
250
     *
251
     * @param string $file
252
     * @return ?string
253
     */
254
    protected function getTokenFromEnvFile(string $file): ?string
255
    {
256
        if (!file_exists($file)) return null;
257
        return DotEnv::load($file)::get('TELEGRAM_API_KEY');
258
    }
259
260
    /**
261
     * Just another echo
262
     *
263
     * @param string $text
264
     * @return void
265
     */
266
    public static function echo(string $text): void
267
    {
268
        echo $text;
269
    }
270
271
}