Passed
Push — master ( 5c7744...96b75b )
by Shahrad
10:30
created

Telegram::setAdmin()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 1
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 3
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
			$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
			if (empty($data['user'])) {
241
				return false;
242
			}
243
244
			$data['user'] = urldecode($data['user']);
245
246
		} else {
247
			$data = json_decode($body, true);
248
249
			if (empty($data['user'])) {
250
				return false;
251
			}
252
253
			$data['user'] = json_encode($data['user']);
254
		}
255
256
		$data_check_string = "auth_date={$data['auth_date']}\nquery_id={$data['query_id']}\nuser={$data['user']}";
257
		$secret_key = hash_hmac('sha256', $token, "WebAppData", true);
258
259
		return hash_hmac('sha256', $data_check_string, $secret_key) == $data['hash'];
260
	}
261
262
	/**
263
	 * Get the update from input
264
	 *
265
	 * @return Update|false
266
	 */
267
	public static function getUpdate(): Update|false
268
	{
269
		$input = self::getInput();
270
		if (empty($input)) return false;
271
		return Telegram::processUpdate($input, self::getApiKey());
272
	}
273
274
	/**
275
	 * Validate the token
276
	 *
277
	 * @param string $token (e.g. 123456789:ABC-DEF1234ghIkl-zyx57W2v1u123ew11) {digit}:{alphanumeric[34]}
278
	 * @return bool
279
	 */
280
	public static function validateToken(string $token): bool
281
	{
282
		preg_match_all('/([0-9]+:[a-zA-Z0-9-_]+)/', $token, $matches);
283
		return count($matches[0]) == 1;
284
	}
285
286
	/**
287
	 * Pass the update to the given webhook handler
288
	 *
289
	 * @param WebhookHandler $webhook_handler The webhook handler
290
	 * @param ?Update $update By default, it will get the update from input
291
	 * @return void
292
	 */
293
	public function fetchWith(WebhookHandler $webhook_handler, ?Update $update = null): void
294
	{
295
		if (is_subclass_of($webhook_handler, WebhookHandler::class)) {
296
			if ($update === null) $update = self::getUpdate();
297
			$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

297
			$webhook_handler->resolve(/** @scrutinizer ignore-type */ $update);
Loading history...
298
		}
299
	}
300
301
	/**
302
	 * Get token from env file.
303
	 *
304
	 * @param string $file
305
	 * @return ?string
306
	 */
307
	protected function getTokenFromEnvFile(string $file): ?string
308
	{
309
		if (!file_exists($file)) return null;
310
		return DotEnv::load($file)::get('TELEGRAM_API_KEY');
311
	}
312
313
	/**
314
	 * Debug mode
315
	 *
316
	 * @param ?int $admin_id Fill this or use setAdmin()
317
	 * @return void
318
	 */
319
	public static function setDebugMode(?int $admin_id = null): void
320
	{
321
		error_reporting(E_ALL);
322
		ini_set('display_errors', 1);
323
		defined('DEBUG_MODE') or define('DEBUG_MODE', true);
324
		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...
325
			defined('TG_ADMIN_ID') or define('TG_ADMIN_ID', $admin_id);
326
		}
327
	}
328
329
	/**
330
	 * Just another echo
331
	 *
332
	 * @param string $text
333
	 * @return void
334
	 */
335
	public static function echo(string $text): void
336
	{
337
		echo $text;
338
	}
339
340
}