Skip to content

Commit b7f47de

Browse files
authored
Merge pull request #13 from devrabie/feature/timeout-and-redis-dedup-13030699252080218284
Add request timeout and Redis duplicate update prevention
2 parents 6205948 + 40125be commit b7f47de

File tree

3 files changed

+96
-5
lines changed

3 files changed

+96
-5
lines changed

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,37 @@ You can also access the Redis instance statically from anywhere in your project:
114114
$redis = \Longman\TelegramBot\Telegram::getRedis();
115115
```
116116

117+
### 3. Prevent Duplicate Updates
118+
119+
Enabling Redis also activates the built-in duplicate update prevention mechanism. This is useful when the bot receives the same update multiple times due to timeouts or retries from Telegram.
120+
121+
When Redis is enabled:
122+
1. The library checks if the `update_id` exists in Redis.
123+
2. If it exists, the update is ignored, and a fake success response is returned to Telegram to stop retries.
124+
3. If it doesn't exist, the `update_id` is stored in Redis with a retention time (TTL) of 60 seconds.
125+
126+
You can customize the retention time (in seconds):
127+
128+
```php
129+
// Set retention time to 120 seconds
130+
Longman\TelegramBot\Telegram::setUpdateRetentionTime(120);
131+
```
132+
133+
---
134+
135+
## ⚙️ Advanced Configuration
136+
137+
### Request Timeout
138+
139+
To prevent your server from hanging on slow Telegram API requests (e.g., cURL error 28), you can set a custom timeout for the HTTP client. The default timeout is 60 seconds.
140+
141+
```php
142+
use Longman\TelegramBot\Request;
143+
144+
// Set request timeout to 30 seconds
145+
Request::setClientTimeout(30);
146+
```
147+
117148
---
118149

119150
## 🔐 Webhook Secret Token

src/Request.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,13 @@ class Request
191191
*/
192192
private static $limiter_interval;
193193

194+
/**
195+
* Guzzle Client timeout
196+
*
197+
* @var int
198+
*/
199+
private static $timeout = 60;
200+
194201
/**
195202
* The current action that is being executed
196203
*
@@ -425,6 +432,16 @@ public static function setCustomBotApiUri(string $api_base_uri, string $api_base
425432
}
426433
}
427434

435+
/**
436+
* Set a custom Guzzle Client timeout
437+
*
438+
* @param int $timeout
439+
*/
440+
public static function setClientTimeout(int $timeout): void
441+
{
442+
self::$timeout = $timeout;
443+
}
444+
428445
/**
429446
* Get input from custom input or stdin and return it
430447
*
@@ -614,8 +631,9 @@ public static function getCurrentAction(): string
614631
*/
615632
public static function execute(string $action, array $data = []): string
616633
{
617-
$request_params = self::setUpRequestParams($data);
618-
$request_params['debug'] = TelegramLog::getDebugLogTempStream();
634+
$request_params = self::setUpRequestParams($data);
635+
$request_params['debug'] = TelegramLog::getDebugLogTempStream();
636+
$request_params['timeout'] = self::$timeout;
619637

620638
try {
621639
$response = self::$client->post(

src/Telegram.php

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ class Telegram
4141
/** @var \Redis|null */
4242
private static $redis_connection;
4343

44+
/**
45+
* Update retention time in Redis (in seconds)
46+
*
47+
* @var int
48+
*/
49+
private static $update_retention_time = 60;
50+
4451
/**
4552
* Telegram API key
4653
*
@@ -558,6 +565,17 @@ public function processUpdate(Update $update): ServerResponse
558565
$this->update = $update;
559566
$this->last_update_id = $update->getUpdateId();
560567

568+
// If Redis is enabled, check if this update has already been processed.
569+
if (self::$redis_connection) {
570+
$redis_key = 'telegram_update_' . $this->last_update_id;
571+
if (self::$redis_connection->get($redis_key)) {
572+
// Return a fake success response to prevent Telegram from retrying.
573+
return new ServerResponse(['ok' => true, 'result' => true], $this->bot_username);
574+
}
575+
// Store the update ID in Redis with a TTL of 60 seconds (matching the default timeout).
576+
self::$redis_connection->setex($redis_key, self::$update_retention_time, '1');
577+
}
578+
561579
if (is_callable($this->update_filter)) {
562580
$reason = 'Update denied by update_filter';
563581
try {
@@ -1117,16 +1135,40 @@ public function enableRedis(array $config = []): Telegram
11171135
];
11181136
}
11191137

1120-
self::$redis_connection = new \Redis();
1121-
self::$redis_connection->connect($config['host'], $config['port']);
1138+
$redis = new \Redis();
1139+
$redis->connect($config['host'], $config['port']);
11221140

11231141
if (!empty($config['password'])) {
1124-
self::$redis_connection->auth($config['password']);
1142+
$redis->auth($config['password']);
11251143
}
11261144

1145+
self::$redis_connection = $redis;
1146+
11271147
return $this;
11281148
}
11291149

1150+
/**
1151+
* Set a custom Redis connection
1152+
*
1153+
* @param \Redis $redis
1154+
* @return void
1155+
*/
1156+
public static function setRedis(\Redis $redis): void
1157+
{
1158+
self::$redis_connection = $redis;
1159+
}
1160+
1161+
/**
1162+
* Set the update retention time in Redis
1163+
*
1164+
* @param int $seconds
1165+
* @return void
1166+
*/
1167+
public static function setUpdateRetentionTime(int $seconds): void
1168+
{
1169+
self::$update_retention_time = $seconds;
1170+
}
1171+
11301172
/**
11311173
* Get the shared Redis client instance.
11321174
*

0 commit comments

Comments
 (0)