diff --git a/docs b/docs index 544e2a897..61cdff1c4 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 544e2a897f399efb7c3bcba84cd872a1ffa6d278 +Subproject commit 61cdff1c445fd53456b63d33a6feac12e4023514 diff --git a/src/Ipc/Client.php b/src/Ipc/Client.php index c21d11e00..8fb55b1d7 100644 --- a/src/Ipc/Client.php +++ b/src/Ipc/Client.php @@ -34,6 +34,7 @@ use danog\MadelineProto\RemoteUrl; use danog\MadelineProto\SessionPaths; use danog\MadelineProto\Wrappers\Start; use Revolt\EventLoop; +use Symfony\Component\Process\InputStream; use Throwable; use function Amp\async; @@ -242,7 +243,10 @@ final class Client extends ClientAbstract $method === 'messages.sendMedia' || $method === 'messages.editMessage') && isset($args['media']['file']) && - $args['media']['file'] instanceof FileCallbackInterface + ( + $args['media']['file'] instanceof FileCallbackInterface + || $args['media']['file'] instanceof InputStream + ) ) { $params = [$method, &$args, $aargs]; $wrapper = Wrapper::create($params, $this->session, $this->logger); @@ -252,7 +256,12 @@ final class Client extends ClientAbstract $params = [$method, &$args, $aargs]; $wrapper = Wrapper::create($params, $this->session, $this->logger); foreach ($args['multi_media'] as &$media) { - if (isset($media['media']['file']) && $media['media']['file'] instanceof FileCallbackInterface) { + if (isset($media['media']['file']) && + ( + $media['media']['file'] instanceof FileCallbackInterface + || $media['media']['file'] instanceof ReadableStream + ) + ) { $wrapper->wrap($media['media']['file'], true); } } diff --git a/src/Ipc/Wrapper.php b/src/Ipc/Wrapper.php index 8a3591882..6131f6a2a 100644 --- a/src/Ipc/Wrapper.php +++ b/src/Ipc/Wrapper.php @@ -115,9 +115,9 @@ final class Wrapper extends ClientAbstract } $class = null; if ($callback instanceof ByteStreamReadableStream) { - $class = \method_exists($callback, 'seek') ? ReadableStream::class : SeekableReadableStream::class; + $class = \method_exists($callback, 'seek') ? SeekableReadableStream::class : ReadableStream::class; } elseif ($callback instanceof ByteStreamWritableStream) { - $class = \method_exists($callback, 'seek') ? WritableStream::class : SeekableWritableStream::class; + $class = \method_exists($callback, 'seek') ? SeekableWritableStream::class : WritableStream::class; } elseif ($callback instanceof FileCallbackInterface) { $class = FileCallback::class; } elseif ($callback instanceof Cancellation) { diff --git a/src/MTProtoSession/CallHandler.php b/src/MTProtoSession/CallHandler.php index f900e6004..90e1b328e 100644 --- a/src/MTProtoSession/CallHandler.php +++ b/src/MTProtoSession/CallHandler.php @@ -130,7 +130,7 @@ trait CallHandler if (isset($args['message']) && \is_string($args['message']) && \mb_strlen($args['message'], 'UTF-8') > ($this->API->getConfig())['message_length_max'] && \mb_strlen($this->API->parseMode($args)['message'], 'UTF-8') > ($this->API->getConfig())['message_length_max']) { $args = $this->API->splitToChunks($args); $promises = []; - $aargs['queue'] = $method; + $aargs['queue'] = $method.' '.time(); $aargs['multiple'] = true; } if (isset($aargs['multiple'])) { diff --git a/src/MTProtoSession/ResponseHandler.php b/src/MTProtoSession/ResponseHandler.php index a63befca3..cf88a3fe5 100644 --- a/src/MTProtoSession/ResponseHandler.php +++ b/src/MTProtoSession/ResponseHandler.php @@ -312,6 +312,16 @@ trait ResponseHandler } EventLoop::queue(closure: $this->methodRecall(...), message_id: $request->getMsgId(), datacenter: $datacenter); return null; + case 400: + if ($request->hasQueue() && + ($response['error_message'] === 'MSG_WAIT_FAILED' + || $response['error_message'] === 'MSG_WAIT_TIMEOUT' + ) + ) { + EventLoop::queue(closure: $this->methodRecall(...), message_id: $request->getMsgId()); + return null; + } + return fn () => new RPCErrorException($response['error_message'], $response['error_code'], $request->getConstructor()); case 401: switch ($response['error_message']) { case 'USER_DEACTIVATED': diff --git a/src/Tools.php b/src/Tools.php index b67317ca7..5629888b0 100644 --- a/src/Tools.php +++ b/src/Tools.php @@ -754,6 +754,13 @@ abstract class Tools extends AsyncTools $name = $call->name->toLowerString(); if (isset(self::BLOCKING_FUNCTIONS[$name])) { + if ($name === 'fopen' && + isset($call->args[0]) && + $call->args[0]->value instanceof String_ && + str_starts_with($call->args[0]->value->value, 'php://memory') + ) { + continue; + } $explanation = self::BLOCKING_FUNCTIONS[$name]; $issues []= new EventHandlerIssue( message: \sprintf(Lang::$current_lang['do_not_use_blocking_function'], $name, $explanation), diff --git a/tests/testing.php b/tests/testing.php index 696f6225e..c131dadc2 100755 --- a/tests/testing.php +++ b/tests/testing.php @@ -15,7 +15,9 @@ If not, see . * Various ways to load MadelineProto. */ +use Amp\ByteStream\ReadableBuffer; use danog\MadelineProto\API; +use danog\MadelineProto\FileCallback; use danog\MadelineProto\Logger; use danog\MadelineProto\Settings; use danog\MadelineProto\VoIP; @@ -260,7 +262,7 @@ $media = []; $media['photo'] = ['_' => 'inputMediaUploadedPhoto', 'file' => __DIR__.'/faust.jpg']; // Image by URL -$media['photo'] = ['_' => 'inputMediaPhotoExternal', 'url' => 'https://github.com/danog/MadelineProto/raw/v8/tests/faust.jpg']; +$media['photo_url'] = ['_' => 'inputMediaPhotoExternal', 'url' => 'https://github.com/danog/MadelineProto/raw/v8/tests/faust.jpg']; // Sticker $media['sticker'] = ['_' => 'inputMediaUploadedDocument', 'file' => __DIR__.'/lel.webp', 'attributes' => [['_' => 'documentAttributeSticker', 'alt' => 'LEL']]]; @@ -278,7 +280,21 @@ $media['voice'] = ['_' => 'inputMediaUploadedDocument', 'file' => __DIR__.'/mosc $media['document'] = ['_' => 'inputMediaUploadedDocument', 'file' => __DIR__.'/60', 'mime_type' => 'magic/magic', 'attributes' => [['_' => 'documentAttributeFilename', 'file_name' => 'magic.magic']]]; // Document by URL -$media['document'] = ['_' => 'inputMediaDocumentExternal', 'url' => 'https://github.com/danog/MadelineProto/raw/v8/tests/60']; +$media['document_url'] = ['_' => 'inputMediaDocumentExternal', 'url' => 'https://github.com/danog/MadelineProto/raw/v8/tests/60']; + +foreach ($media as $key => $value) { + if (isset($value['file'])) { + $value['file'] = new ReadableBuffer(read($value['file'])); + $media["stream_$key"] = $value; + } +} + +foreach ($media as $key => $value) { + if (isset($value['file'])) { + $value['file'] = new FileCallback($value['file'], function () {}); + $media["callback_$key"] = $value; + } +} $message = 'yay '.PHP_VERSION_ID; $mention = $MadelineProto->getInfo(getenv('TEST_USERNAME')); // Returns an array with all of the constructors that can be extracted from a username or an id @@ -311,8 +327,8 @@ foreach ($peers as $peer) { $MadelineProto->logger("Downloading $type"); $file = $MadelineProto->downloadToDir($dl, '/tmp'); - if ($type !== 'photo') { - Assert::eq(read($file), $fileOrig, "Not equal!"); + if ($type !== 'photo' && $type !== 'photo_url') { + Assert::eq(read($file), $fileOrig, "Not equal $type!"); } $MadelineProto->logger("Uploading $type"); @@ -320,8 +336,8 @@ foreach ($peers as $peer) { $MadelineProto->logger("Downloading $type"); $file = $MadelineProto->downloadToDir($media, '/tmp'); - if ($type !== 'photo') { - Assert::eq(read($file), $fileOrig, "Not equal!"); + if ($type !== 'photo' && $type !== 'photo_url') { + Assert::eq(read($file), $fileOrig, "Not equal $type!"); } $MadelineProto->logger("Re-sending $type"); @@ -331,8 +347,8 @@ foreach ($peers as $peer) { $MadelineProto->logger("Re-downloading $type"); $file = $MadelineProto->downloadToDir($dl, '/tmp'); - if ($type !== 'photo') { - Assert::eq(read($file), $fileOrig, "Not equal!"); + if ($type !== 'photo' && $type !== 'photo_url') { + Assert::eq(read($file), $fileOrig, "Not equal $type!"); } } }