diff --git a/src/MTProtoSession/ResponseHandler.php b/src/MTProtoSession/ResponseHandler.php index 345c0ea31..ea2f14a6a 100644 --- a/src/MTProtoSession/ResponseHandler.php +++ b/src/MTProtoSession/ResponseHandler.php @@ -31,7 +31,9 @@ use danog\MadelineProto\MTProto; use danog\MadelineProto\MTProto\MTProtoIncomingMessage; use danog\MadelineProto\MTProto\MTProtoOutgoingMessage; use danog\MadelineProto\PTSException; +use danog\MadelineProto\RPCError\FloodPremiumWaitError; use danog\MadelineProto\RPCError\FloodWaitError; +use danog\MadelineProto\RPCError\RateLimitError; use danog\MadelineProto\RPCErrorException; use danog\MadelineProto\SecretPeerNotInDbException; use danog\MadelineProto\SecurityException; @@ -451,9 +453,9 @@ trait ResponseHandler } return static fn () => RPCErrorException::make($response['error_message'], $response['error_code'], $request->constructor); case 420: - $seconds = preg_replace('/[^0-9]+/', '', $response['error_message']); + $seconds = (int) preg_replace('/[^0-9]+/', '', $response['error_message']); $limit = $request->floodWaitLimit ?? $this->API->settings->getRPC()->getFloodTimeout(); - if (is_numeric($seconds) && $seconds < $limit) { + if ($seconds < $limit) { $this->API->logger("Flood, waiting $seconds seconds before repeating async call of $request...", Logger::NOTICE); $this->gotResponseForOutgoingMessage($request); $msgId = $request->getMsgId(); @@ -466,9 +468,27 @@ trait ResponseHandler return null; } if (str_starts_with($response['error_message'], 'FLOOD_WAIT_')) { - return static fn () => new FloodWaitError($response['error_message'], $response['error_code'], $request->constructor); + return static fn () => new FloodWaitError( + $response['error_message'], + $seconds, + $response['error_code'], + $request->constructor + ); } - // no break + if (str_starts_with($response['error_message'], 'FLOOD_PREMIUM_WAIT_')) { + return static fn () => new FloodPremiumWaitError( + $response['error_message'], + $seconds, + $response['error_code'], + $request->constructor + ); + } + return static fn () => new RateLimitError( + $response['error_message'], + $seconds, + $response['error_code'], + $request->constructor + ); default: return static fn () => RPCErrorException::make($response['error_message'], $response['error_code'], $request->constructor); } diff --git a/src/MTProtoTools/Files.php b/src/MTProtoTools/Files.php index 9c726e518..2a1d5b011 100644 --- a/src/MTProtoTools/Files.php +++ b/src/MTProtoTools/Files.php @@ -48,6 +48,7 @@ use danog\MadelineProto\FileRedirect; use danog\MadelineProto\Logger; use danog\MadelineProto\MTProtoTools\Crypt\IGE; use danog\MadelineProto\RPCError\FileTokenInvalidError; +use danog\MadelineProto\RPCError\FloodPremiumWaitError; use danog\MadelineProto\RPCError\FloodWaitError; use danog\MadelineProto\RPCErrorException; use danog\MadelineProto\SecurityException; @@ -358,6 +359,12 @@ trait Files } $d->complete(); return; + } catch (FloodPremiumWaitError $e) { + $this->logger("Got {$e->rpc} while uploading $part_num: {$datacenter}, retrying..."); + $writePromise = async(static function () use ($cancellation, $e, $writeCb): void { + $e->wait($cancellation); + $writeCb(); + }); } catch (FileRedirect $e) { $datacenter = $e->dc; $this->logger("Got redirect while uploading $part_num: {$datacenter}"); @@ -1210,8 +1217,10 @@ trait Files break; } catch (FileRedirect $e) { $datacenter = $e->dc; - } catch (FloodWaitError $e) { + } catch (FloodWaitError) { delay(1, cancellation: $cancellation); + } catch (FloodPremiumWaitError $e) { + $e->wait($cancellation); } catch (FileTokenInvalidError) { $cdn = false; $datacenter = $this->authorized_dc; diff --git a/src/RPCError/FloodPremiumWaitError.php b/src/RPCError/FloodPremiumWaitError.php new file mode 100644 index 000000000..a1ea32bfe --- /dev/null +++ b/src/RPCError/FloodPremiumWaitError.php @@ -0,0 +1,26 @@ +. + * + * @author Daniil Gentili + * @copyright 2016-2023 Daniil Gentili + * @license https://opensource.org/licenses/AGPL-3.0 AGPLv3 + * @link https://docs.madelineproto.xyz MadelineProto documentation + */ + +namespace danog\MadelineProto\RPCError; + +/** + * Represents a FLOOD_PREMIUM_WAIT_ RPC error returned by telegram. + */ +final class FloodPremiumWaitError extends RateLimitError +{ +} diff --git a/src/RPCError/FloodWaitError.php b/src/RPCError/FloodWaitError.php index e3ac5fbcb..e253a6db3 100644 --- a/src/RPCError/FloodWaitError.php +++ b/src/RPCError/FloodWaitError.php @@ -18,40 +18,9 @@ namespace danog\MadelineProto\RPCError; -use danog\MadelineProto\RPCErrorException; -use Exception; -use Webmozart\Assert\Assert; - -use function Amp\delay; - /** * Represents a FLOOD_WAIT_ RPC error returned by telegram. */ -final class FloodWaitError extends RPCErrorException +final class FloodWaitError extends RateLimitError { - public readonly int $waitTime; - public function __construct(string $message, int $code, string $caller, ?Exception $previous = null) - { - Assert::true(str_starts_with($message, 'FLOOD_WAIT_')); - $seconds = substr($message, 11); - Assert::numeric($seconds); - $this->waitTime = (int) $seconds; - parent::__construct($message, "A rate limit was encountered, please repeat the method call after $seconds seconds", $code, $caller, $previous); - } - - /** - * Returns the required waiting period in seconds before repeating the RPC call. - */ - public function getWaitTime(): int - { - return $this->waitTime; - } - - /** - * Waits for the required waiting period. - */ - public function wait(): void - { - delay($this->waitTime); - } } diff --git a/src/RPCError/RateLimitError.php b/src/RPCError/RateLimitError.php new file mode 100644 index 000000000..87f903daf --- /dev/null +++ b/src/RPCError/RateLimitError.php @@ -0,0 +1,53 @@ +. + * + * @author Daniil Gentili + * @copyright 2016-2023 Daniil Gentili + * @license https://opensource.org/licenses/AGPL-3.0 AGPLv3 + * @link https://docs.madelineproto.xyz MadelineProto documentation + */ + +namespace danog\MadelineProto\RPCError; + +use Amp\Cancellation; +use danog\MadelineProto\RPCErrorException; +use Exception; + +use function Amp\delay; + +/** + * Represents a rate limiting RPC error returned by telegram. + */ +class RateLimitError extends RPCErrorException +{ + /** @internal */ + public function __construct(string $message, public readonly int $waitTime, int $code, string $caller, ?Exception $previous = null) + { + parent::__construct($message, "A rate limit was encountered, please repeat the method call after $waitTime seconds", $code, $caller, $previous); + } + + /** + * Returns the required waiting period in seconds before repeating the RPC call. + */ + public function getWaitTime(): int + { + return $this->waitTime; + } + + /** + * Waits for the required waiting period. + */ + public function wait(?Cancellation $cancellation = null): void + { + delay($this->waitTime, cancellation: $cancellation); + } +} diff --git a/src/RPCErrorException.php b/src/RPCErrorException.php index 2b978b873..b983fe447 100644 --- a/src/RPCErrorException.php +++ b/src/RPCErrorException.php @@ -130,6 +130,7 @@ class RPCErrorException extends \Exception 'VOLUME_LOC_NOT_FOUND' => true, 'FILE_WRITE_EMPTY' => true, 'Internal_Server_Error' => true, + 'INVITE_HASH_UNSYNC' => true, ]; /** @internal */ diff --git a/tools/build_docs.php b/tools/build_docs.php index 0a874c7ce..0f1007cba 100755 --- a/tools/build_docs.php +++ b/tools/build_docs.php @@ -57,6 +57,8 @@ require 'vendor/autoload.php'; `rm -r src/RPCError/*`; `git checkout src/RPCError/FloodWaitError.php`; +`git checkout src/RPCError/FloodPremiumWaitError.php`; +`git checkout src/RPCError/RateLimitError.php`; $map = [];