Passed
Push — master ( fd6438...0db353 )
by Shahrad
02:02
created

Telegram::getEnvFilePath()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
dl 0
loc 15
c 1
b 0
f 0
rs 10
cc 3
nc 3
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\Traits\TelegramTrait;
10
use TelegramBot\Traits\WebhookTrait;
11
use TelegramBot\Util\Toolkit;
12
13
/**
14
 * Telegram class
15
 *
16
 * @link    https://github.com/telegram-bot-php/core
17
 * @author  Shahrad Elahi (https://github.com/shahradelahi)
18
 * @license https://github.com/telegram-bot-php/core/blob/master/LICENSE (MIT License)
19
 */
20
class Telegram
21
{
22
23
    use TelegramTrait;
24
    use WebhookTrait;
25
26
    /**
27
     * @var string
28
     */
29
    public static string $VERSION = 'v1.0.0';
30
31
    /**
32
     * @var string|null
33
     */
34
    private static string|null $api_key = null;
35
36
    /**
37
     * Telegram constructor.
38
     *
39
     * @param string $api_token
40
     */
41
    public function __construct(string $api_token = '')
42
    {
43
        if ($api_token === '') {
44
            (new Dotenv())->load($this->getEnvFilePath());
45
            $api_token = $_ENV['TELEGRAM_BOT_TOKEN'];
46
        }
47
48
        if (empty($api_token) || self::validateToken($api_token) === false) {
49
            throw new TelegramException(sprintf(
50
                'Invalid Telegram API token: %s',
51
                $api_token
52
            ));
53
        }
54
55
        self::setToken($api_token);
56
    }
57
58
    /**
59
     * Set the current bot token.
60
     *
61
     * @param string $api_key
62
     * @return void
63
     */
64
    public static function setToken(string $api_key): void
65
    {
66
        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...
67
        $_ENV['TELEGRAM_BOT_TOKEN'] = $api_key;
68
    }
69
70
    /**
71
     * Get the current bot token.
72
     *
73
     * @return string|false
74
     */
75
    public static function getApiToken(): string|false
76
    {
77
        return self::$api_key !== null ? (self::validateToken(self::$api_key) ? self::$api_key : false) : false;
78
    }
79
80
    /**
81
     * Get bot info from given API key
82
     *
83
     * @return Response
84
     * @throws TelegramException
85
     */
86
    public function getInfo(): Response
87
    {
88
        $result = Request::getMe();
89
90
        if (!$result->isOk()) {
91
            throw new TelegramException($result->getErrorCode() . ': ' . $result->getDescription());
92
        }
93
94
        return $result;
95
    }
96
97
    /**
98
     * Pass the update to the given update handler
99
     *
100
     * @param UpdateHandler $update_handler The update handler
101
     * @param Update|null $update By default, it will get the update from input
102
     * @return void
103
     */
104
    public function fetchWith(UpdateHandler $update_handler, Update|null $update = null): void
105
    {
106
        if (is_subclass_of($update_handler, UpdateHandler::class)) {
107
            if ($update === null) $update = self::getUpdate();
108
            $update_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

108
            $update_handler->resolve(/** @scrutinizer ignore-type */ $update);
Loading history...
109
        }
110
    }
111
112
    /**
113
     * Get the update from input
114
     *
115
     * @return Update|false
116
     */
117
    public static function getUpdate(): Update|false
118
    {
119
        $input = self::getInput();
120
        if (empty($input)) return false;
121
        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

121
        return Telegram::processUpdate($input, /** @scrutinizer ignore-type */ self::getApiToken());
Loading history...
122
    }
123
124
    /**
125
     * Get input from stdin and return it
126
     *
127
     * @return string|null
128
     */
129
    public static function getInput(): string|null
130
    {
131
        return file_get_contents('php://input') ?? null;
132
    }
133
134
    /**
135
     * This method will convert a string to an update object
136
     *
137
     * @param string $input The input string
138
     * @param string|null $apiKey The API key
139
     * @return Update|false
140
     */
141
    public static function processUpdate(string $input, string|null $apiKey = null): Update|false
142
    {
143
        if (empty($input)) {
144
            throw new TelegramException(
145
                'Input is empty! Please check your code and try again.'
146
            );
147
        }
148
149
        if ($apiKey !== null && !self::validateToken($apiKey)) {
150
            throw new TelegramException(
151
                'Invalid token! Please check your code and try again.'
152
            );
153
        }
154
155
        if ($apiKey !== null && self::validateWebData($apiKey, $input)) {
156
            if (Toolkit::isUrlEncode($input)) {
157
                $web_data = Toolkit::urlDecode($input);
158
            }
159
160
            if (Toolkit::isJson($input)) {
161
                $web_data = json_decode($input, true);
162
            }
163
164
            if (!empty($web_data) && is_array($web_data)) {
165
                $input = json_encode([
166
                    'web_data' => $web_data,
167
                ]);
168
            }
169
        }
170
171
        if (!Toolkit::isJson($input)) {
172
            throw new TelegramException(sprintf(
173
                "Input is not a valid JSON string! Please check your code and try again.\nInput: %s",
174
                $input
175
            ));
176
        }
177
178
        $input = json_decode($input, true);
179
180
        return new Update($input);
181
    }
182
183
    /**
184
     * Validate the token
185
     *
186
     * @param string $token (e.g. 123456789:ABC-DEF1234ghIkl-zyx57W2v1u123ew11) {digit}:{alphanumeric[34]}
187
     * @return bool
188
     */
189
    public static function validateToken(string $token): bool
190
    {
191
        preg_match_all('/([0-9]+:[a-zA-Z0-9-_]+)/', $token, $matches);
192
        return count($matches[0]) == 1;
193
    }
194
195
    /**
196
     * Validate webapp data from is from Telegram
197
     *
198
     * @link https://core.telegram.org/bots/webapps#validating-data-received-via-the-web-app
199
     *
200
     * @param string $token The bot token
201
     * @param string $body The message body from getInput()
202
     * @return bool
203
     */
204
    public static function validateWebData(string $token, string $body): bool
205
    {
206
        if (!Toolkit::isJson($body)) {
207
            $raw_data = rawurldecode(str_replace('_auth=', '', $body));
208
            $data = Toolkit::urlDecode($raw_data);
209
210
            if (empty($data['user'])) {
211
                return false;
212
            }
213
214
            $data['user'] = urldecode($data['user']);
215
216
        } else {
217
            $data = json_decode($body, true);
218
219
            if (empty($data['user'])) {
220
                return false;
221
            }
222
223
            $data['user'] = json_encode($data['user']);
224
        }
225
226
        $data_check_string = "auth_date={$data['auth_date']}\nquery_id={$data['query_id']}\nuser={$data['user']}";
227
        $secret_key = hash_hmac('sha256', $token, "WebAppData", true);
228
229
        return hash_hmac('sha256', $data_check_string, $secret_key) == $data['hash'];
230
    }
231
232
}