diff --git a/examples/bot.php b/examples/bot.php index 10a18824a..ea22cb369 100755 --- a/examples/bot.php +++ b/examples/bot.php @@ -35,7 +35,6 @@ use danog\MadelineProto\EventHandler\Plugin\RestartPlugin; use danog\MadelineProto\EventHandler\SimpleFilter\FromAdmin; use danog\MadelineProto\EventHandler\SimpleFilter\Incoming; use danog\MadelineProto\EventHandler\SimpleFilter\IsReply; -use danog\MadelineProto\LocalFile; use danog\MadelineProto\Logger; use danog\MadelineProto\ParseMode; use danog\MadelineProto\RemoteUrl; diff --git a/examples/secret_bot.php b/examples/secret_bot.php index 5cfef48de..5e8c6b8b0 100755 --- a/examples/secret_bot.php +++ b/examples/secret_bot.php @@ -21,14 +21,11 @@ use danog\MadelineProto\EventHandler\Attributes\Handler; use danog\MadelineProto\EventHandler\Message\PrivateMessage; use danog\MadelineProto\EventHandler\Message\SecretMessage; use danog\MadelineProto\EventHandler\SimpleFilter\Incoming; +use danog\MadelineProto\LocalFile; use danog\MadelineProto\Logger; use danog\MadelineProto\Settings; use danog\MadelineProto\SimpleEventHandler; -use function Amp\async; -use function Amp\File\read; -use function Amp\Future\await; - /* * Various ways to load MadelineProto */ @@ -86,234 +83,51 @@ class SecretHandler extends SimpleEventHandler if (isset($this->sent[$update->chatId])) { return; } - $secret_media = []; - - // Photo uploaded as document, secret chat - $secret_media['document_photo'] = [ - 'peer' => $update->chatId, - 'file' => 'tests/faust.jpg', - 'message' => [ - '_' => 'decryptedMessage', - 'ttl' => 0, - 'message' => '', - 'media' => [ - '_' => 'decryptedMessageMediaDocument', - 'thumb' => read('tests/faust.preview.jpg'), - 'thumb_w' => 90, - 'thumb_h' => 90, - 'mime_type' => mime_content_type('tests/faust.jpg'), - 'caption' => 'This file was uploaded using MadelineProto', - 'file_name' => 'faust.jpg', - 'size' => filesize('tests/faust.jpg'), - 'attributes' => [ - [ - '_' => 'documentAttributeImageSize', - 'w' => 1280, - 'h' => 914, - ], - ], - ], - ], - ]; // Photo, secret chat - $secret_media['photo'] = [ - 'peer' => $update->chatId, - 'file' => 'tests/faust.jpg', - 'message' => [ - '_' => 'decryptedMessage', - 'ttl' => 0, - 'message' => '', - 'media' => [ - '_' => 'decryptedMessageMediaPhoto', - 'thumb' => read('tests/faust.preview.jpg'), - 'thumb_w' => 90, - 'thumb_h' => 90, - 'caption' => 'This file was uploaded using MadelineProto', - 'size' => filesize('tests/faust.jpg'), - 'w' => 1280, - 'h' => 914, - ], - ], - ]; + $this->sendPhoto( + peer: $update->chatId, + file: new LocalFile('tests/faust.jpg'), + caption: 'This file was uploaded using MadelineProto', + ); // GIF, secret chat - $secret_media['gif'] = [ - 'peer' => $update->chatId, - 'file' => 'tests/pony.mp4', - 'message' => [ - '_' => 'decryptedMessage', - 'ttl' => 0, - 'message' => '', - 'media' => [ - '_' => 'decryptedMessageMediaDocument', - 'thumb' => read('tests/pony.preview.jpg'), - 'thumb_w' => 90, - 'thumb_h' => 90, - 'mime_type' => mime_content_type('tests/pony.mp4'), - 'caption' => 'test', - 'file_name' => 'pony.mp4', - 'size' => filesize('tests/faust.jpg'), - 'attributes' => [ - ['_' => 'documentAttributeAnimated'], - ], - ], - ], - ]; + $this->sendGif( + peer: $update->chatId, + file: new LocalFile('tests/pony.mp4'), + caption: 'This file was uploaded using MadelineProto', + ); // Sticker, secret chat - $secret_media['sticker'] = [ - 'peer' => $update->chatId, - 'file' => 'tests/lel.webp', - 'message' => [ - '_' => 'decryptedMessage', - 'ttl' => 0, - 'message' => '', - 'media' => [ - '_' => 'decryptedMessageMediaDocument', - 'thumb' => read('tests/lel.preview.jpg'), - 'thumb_w' => 90, - 'thumb_h' => 90, - 'mime_type' => mime_content_type('tests/lel.webp'), - 'caption' => 'test', - 'file_name' => 'lel.webp', - 'size' => filesize('tests/lel.webp'), - 'attributes' => [ - [ - '_' => 'documentAttributeImageSize', - 'w' => 512, - 'h' => 510, - ], - [ - '_' => 'documentAttributeSticker', - 'alt' => 'LEL', - 'stickerset' => ['_' => 'inputStickerSetEmpty'], - ], - ], - ], - ], - ]; + $this->sendSticker( + peer: $update->chatId, + file: new LocalFile('tests/lel.webp'), + mimeType: "image/webp" + ); - // Document, secrey chat - $secret_media['document'] = [ - 'peer' => $update->chatId, - 'file' => 'tests/60', - 'message' => [ - '_' => 'decryptedMessage', - 'ttl' => 0, - 'message' => '', - 'media' => [ - '_' => 'decryptedMessageMediaDocument', - 'thumb' => read('tests/faust.preview.jpg'), - 'thumb_w' => 90, - 'thumb_h' => 90, - 'mime_type' => 'magic/magic', - 'caption' => 'test', - 'file_name' => 'magic.magic', - 'size' => filesize('tests/60'), - 'attributes' => [ - [ - '_' => 'documentAttributeFilename', - 'file_name' => 'fairy', - ], - ], - ], - ], - ]; + // Document, secret chat + $this->sendDocument( + peer: $update->chatId, + file: new LocalFile('tests/60'), + fileName: 'fairy' + ); // Video, secret chat - $secret_media['video'] = [ - 'peer' => $update->chatId, - 'file' => 'tests/swing.mp4', - 'message' => [ - '_' => 'decryptedMessage', - 'ttl' => 0, - 'message' => '', - 'media' => [ - '_' => 'decryptedMessageMediaDocument', - 'thumb' => read('tests/swing.preview.jpg'), - 'thumb_w' => 90, - 'thumb_h' => 90, - 'mime_type' => mime_content_type('tests/swing.mp4'), - 'caption' => 'test', - 'file_name' => 'swing.mp4', - 'size' => filesize('tests/swing.mp4'), - 'attributes' => [ - [ - '_' => 'documentAttributeVideo', - 'duration' => 5, - 'w' => 1280, - 'h' => 720, - ], - ], - ], - ], - ]; + $this->sendVideo( + peer: $update->chatId, + file: new LocalFile('tests/swing.mp4'), + ); // audio, secret chat - $secret_media['audio'] = [ - 'peer' => $update->chatId, - 'file' => 'tests/mosconi.mp3', - 'message' => [ - '_' => 'decryptedMessage', - 'ttl' => 0, - 'message' => '', - 'media' => [ - '_' => 'decryptedMessageMediaDocument', - 'thumb' => read('tests/faust.preview.jpg'), - 'thumb_w' => 90, - 'thumb_h' => 90, - 'mime_type' => mime_content_type('tests/mosconi.mp3'), - 'caption' => 'test', - 'file_name' => 'mosconi.mp3', - 'size' => filesize('tests/mosconi.mp3'), - 'attributes' => [ - [ - '_' => 'documentAttributeAudio', - 'voice' => false, - 'duration' => 1, - 'title' => 'AH NON LO SO IO', - 'performer' => 'IL DIO GERMANO MOSCONI', - ], - ], - ], - ], - ]; + $this->sendAudio( + peer: $update->chatId, + file: new LocalFile('tests/mosconi.mp3'), + ); - $secret_media['voice'] = [ - 'peer' => $update->chatId, - 'file' => 'tests/mosconi.mp3', - 'message' => [ - '_' => 'decryptedMessage', - 'ttl' => 0, - 'message' => '', - 'media' => [ - '_' => 'decryptedMessageMediaDocument', - 'thumb' => read('tests/faust.preview.jpg'), - 'thumb_w' => 90, - 'thumb_h' => 90, - 'mime_type' => mime_content_type('tests/mosconi.mp3'), - 'caption' => 'test', - 'file_name' => 'mosconi.mp3', - 'size' => filesize('tests/mosconi.mp3'), - 'attributes' => [ - [ - '_' => 'documentAttributeAudio', - 'voice' => true, - 'duration' => 1, - 'title' => 'AH NON LO SO IO', - 'performer' => 'IL DIO GERMANO MOSCONI', - ], - ], - ], - ], - ]; - - $promises = []; - foreach ($secret_media as $type => $smessage) { - $promises []= async($this->messages->sendEncryptedFile(...), $smessage); - } - await($promises); + $this->sendVoice( + peer: $update->chatId, + file: new LocalFile('tests/mosconi.mp3'), + ); $i = 0; while ($i < 10) { diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 35bbb516f..800d1cdd2 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -4115,6 +4115,8 @@ + + @@ -5483,7 +5485,7 @@ - + diff --git a/src/EventHandler/Media/AbstractVideo.php b/src/EventHandler/Media/AbstractVideo.php index 3b5ad3495..d55bdae6a 100644 --- a/src/EventHandler/Media/AbstractVideo.php +++ b/src/EventHandler/Media/AbstractVideo.php @@ -44,9 +44,9 @@ abstract class AbstractVideo extends Media bool $protected, ) { parent::__construct($API, $rawMedia, $protected); - $this->duration = $attribute['duration']; + $this->duration = $attribute['duration'] ?? 0.0; $this->supportsStreaming = $attribute['supports_streaming'] ?? false; - $this->width = $attribute['w']; - $this->height = $attribute['h']; + $this->width = $attribute['w'] ?? 0; + $this->height = $attribute['h'] ?? 0; } } diff --git a/src/InternalDoc.php b/src/InternalDoc.php index 127bbe501..174b8bdef 100644 --- a/src/InternalDoc.php +++ b/src/InternalDoc.php @@ -1836,9 +1836,11 @@ abstract class InternalDoc * @param Cancellation $cancellation Cancellation. * */ - final public function sendSticker(string|int $peer, \danog\MadelineProto\EventHandler\Message|\danog\MadelineProto\EventHandler\Media|\danog\MadelineProto\LocalFile|\danog\MadelineProto\RemoteUrl|\danog\MadelineProto\BotApiFileId|\Amp\ByteStream\ReadableStream $file, string $emoji = '', ?callable $callback = null, ?string $fileName = null, ?string $mimeType = null, ?int $ttl = null, ?int $replyToMsgId = null, ?int $topMsgId = null, ?array $replyMarkup = null, string|int|null $sendAs = null, ?int $scheduleDate = null, bool $silent = false, bool $noForwards = false, bool $background = false, bool $clearDraft = false, bool $updateStickersetsOrder = false, bool $forceResend = false, ?\Amp\Cancellation $cancellation = null): \danog\MadelineProto\EventHandler\Message + final public function sendSticker(string|int $peer, \danog\MadelineProto\EventHandler\Message|\danog\MadelineProto\EventHandler\Media|\danog\MadelineProto\LocalFile|\danog\MadelineProto\RemoteUrl|\danog\MadelineProto\BotApiFileId|\Amp\ByteStream\ReadableStream $file, string $mimeType, string $emoji = '', array $stickerSet = [ + '_' => 'inputStickerSetEmpty', + ], ?callable $callback = null, ?string $fileName = null, ?int $ttl = null, ?int $replyToMsgId = null, ?int $topMsgId = null, ?array $replyMarkup = null, string|int|null $sendAs = null, ?int $scheduleDate = null, bool $silent = false, bool $noForwards = false, bool $background = false, bool $clearDraft = false, bool $updateStickersetsOrder = false, bool $forceResend = false, ?\Amp\Cancellation $cancellation = null): \danog\MadelineProto\EventHandler\Message { - return $this->wrapper->getAPI()->sendSticker($peer, $file, $emoji, $callback, $fileName, $mimeType, $ttl, $replyToMsgId, $topMsgId, $replyMarkup, $sendAs, $scheduleDate, $silent, $noForwards, $background, $clearDraft, $updateStickersetsOrder, $forceResend, $cancellation); + return $this->wrapper->getAPI()->sendSticker($peer, $file, $mimeType, $emoji, $stickerSet, $callback, $fileName, $ttl, $replyToMsgId, $topMsgId, $replyMarkup, $sendAs, $scheduleDate, $silent, $noForwards, $background, $clearDraft, $updateStickersetsOrder, $forceResend, $cancellation); } /** * Sends a video. diff --git a/src/MTProtoTools/FilesAbstraction.php b/src/MTProtoTools/FilesAbstraction.php index b739b1461..5b5cd29f2 100644 --- a/src/MTProtoTools/FilesAbstraction.php +++ b/src/MTProtoTools/FilesAbstraction.php @@ -237,10 +237,11 @@ trait FilesAbstraction public function sendSticker( int|string $peer, Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file, + string $mimeType, string $emoji = '', + array $stickerSet = ['_' => 'inputStickerSetEmpty'], ?callable $callback = null, ?string $fileName = null, - ?string $mimeType = null, ?int $ttl = null, ?int $replyToMsgId = null, ?int $topMsgId = null, @@ -259,10 +260,10 @@ trait FilesAbstraction type: Sticker::class, mimeType: $mimeType, thumb: null, - attributesOrig: [], + attributesOrig: ['_' => 'documentAttributeSticker', 'alt' => $emoji, 'stickerset' => $stickerSet], peer: $peer, file: $file, - caption: $emoji, + caption: '', parseMode: ParseMode::TEXT, callback: $callback, fileName: $fileName, @@ -760,6 +761,7 @@ trait FilesAbstraction : $attributesOrig['waveform'], ], ], + Sticker::class => [$attributesOrig], default => [], }; if ($type === Gif::class) { @@ -791,7 +793,7 @@ trait FilesAbstraction $width = 0; $height = 0; - if ($type === Photo::class) { + if ($type === Photo::class || $type === Sticker::class) { if (!\extension_loaded('gd')) { throw Exception::extension('gd'); } @@ -799,34 +801,36 @@ trait FilesAbstraction $img = imagecreatefromstring($file); $width = imagesx($img); $height = imagesy($img); - if ($width > $height) { - $thumb_width = 90; - $thumb_height = (int) (90*$height/$width); - } elseif ($width < $height) { - $thumb_width = (int) (90*$width/$height); - $thumb_height = 90; - } else { - $thumb_width = 90; - $thumb_height = 90; - } - Assert::lessThanEq($thumb_height, 90); - Assert::lessThanEq($thumb_width, 90); - $thumb = imagecreatetruecolor($thumb_width, $thumb_height); - imagecopyresized($thumb, $img, 0, 0, 0, 0, $thumb_width, $thumb_height, $width, $height); - - $stream = fopen('php://memory', 'r+'); - imagepng($thumb, $stream); - rewind($stream); - $thumb = stream_get_contents($stream); - fclose($stream); - unset($stream); $file = new ReadableBuffer($file); + if ($type === Photo::class) { + if ($width > $height) { + $thumb_width = 90; + $thumb_height = (int) (90*$height/$width); + } elseif ($width < $height) { + $thumb_width = (int) (90*$width/$height); + $thumb_height = 90; + } else { + $thumb_width = 90; + $thumb_height = 90; + } + Assert::lessThanEq($thumb_height, 90); + Assert::lessThanEq($thumb_width, 90); + $thumb = imagecreatetruecolor($thumb_width, $thumb_height); + imagecopyresized($thumb, $img, 0, 0, 0, 0, $thumb_width, $thumb_height, $width, $height); + + $stream = fopen('php://memory', 'r+'); + imagepng($thumb, $stream); + rewind($stream); + $thumb = stream_get_contents($stream); + fclose($stream); + unset($stream); + } } elseif ($type === Video::class || $type === Gif::class) { $this->extractVideoInfo(true, $attributesOrig['thumbSeek'], $file, $fileName, $callback, $cancellation, $mimeType, $attributes, $thumb); } elseif ($type === Audio::class || $type === Voice::class) { $this->extractAudioInfo(true, $file, $fileName, $callback, $cancellation, $mimeType, $attributes, $thumb); } elseif ($mimeType === null) { - $mimeType = $this->extractMime($file, $fileName, $callback, $cancellation); + $mimeType = $this->extractMime(true, $file, $fileName, $callback, $cancellation); } if ($thumb !== null && $thumb_width === 0) { @@ -904,7 +908,7 @@ trait FilesAbstraction } elseif ($type === Audio::class || $type === Voice::class) { $this->extractAudioInfo(false, $file, $fileName, $callback, $cancellation, $mimeType, $attributes, $thumb); } elseif ($mimeType === null) { - $mimeType = $this->extractMime($file, $fileName, $callback, $cancellation); + $mimeType = $this->extractMime(false, $file, $fileName, $callback, $cancellation); } $method = 'messages.sendMedia'; @@ -1002,14 +1006,11 @@ trait FilesAbstraction return $res; } - /** - * @return list{array, string} - */ - private function extractMime(Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file, ?string $fileName, ?callable $callback, ?Cancellation $cancellation): array + private function extractMime(bool $secret, Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream &$file, ?string $fileName, ?callable $callback, ?Cancellation $cancellation): string { $file = $this->getStream($file, $cancellation); $p = new Pipe(1024*1024); - $fileFuture = async(fn () => $this->upload(new StreamDuplicator($file, $p->getSink()), $fileName ?? '', $callback, cancellation: $cancellation)); + $fileFuture = async(fn () => $this->upload(new StreamDuplicator($file, $p->getSink()), $fileName ?? '', $callback, $secret, $cancellation)); $buff = ''; while (\strlen($buff) < 1024*1024 && null !== $chunk = $p->getSource()->read($cancellation)) { @@ -1019,7 +1020,8 @@ trait FilesAbstraction $p->getSource()->close(); unset($p); - return [$fileFuture->await(), (new finfo())->buffer($buff, FILEINFO_MIME_TYPE)]; + $file = $fileFuture->await(); + return (new finfo())->buffer($buff, FILEINFO_MIME_TYPE); } private function extractAudioInfo(bool $secret, Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream &$file, ?string $fileName, ?callable $callback, ?Cancellation $cancellation, ?string &$mimeType, array &$attributes, mixed &$thumb): void { @@ -1029,7 +1031,7 @@ trait FilesAbstraction } $this->logger->logger('Install ffmpeg for audio info extraction!'); if ($mimeType === null) { - [$file, $mimeType] = $this->extractMime($file, $fileName, $callback, $cancellation); + $mimeType = $this->extractMime($secret, $file, $fileName, $callback, $cancellation); } return; } @@ -1102,7 +1104,7 @@ trait FilesAbstraction } $this->logger->logger('Install ffmpeg for video info extraction!'); if ($mimeType === null) { - [$file, $mimeType] = $this->extractMime($file, $fileName, $callback, $cancellation); + $mimeType = $this->extractMime($secret, $file, $fileName, $callback, $cancellation); } return; } @@ -1143,7 +1145,9 @@ trait FilesAbstraction $fileFuture = async(fn () => $this->upload(new StreamDuplicator($file, ...$streams), $fileName ?? '', $callback, $secret, $cancellation)); [$stdout, $stderr] = await($f); - $thumb ??= new ReadableBuffer($stdout); + if ($stdout !== '') { + $thumb ??= new ReadableBuffer($stdout); + } $process->join($cancellation); if (preg_match('~Duration: (\d{2}:\d{2}:\d{2}\.\d{2}),.*? (\d{3,4})x(\d{3,4})~s', $stderr, $matches)) { diff --git a/src/SecretChats/AuthKeyHandler.php b/src/SecretChats/AuthKeyHandler.php index 39d63678a..8f062a82e 100644 --- a/src/SecretChats/AuthKeyHandler.php +++ b/src/SecretChats/AuthKeyHandler.php @@ -67,7 +67,8 @@ trait AuthKeyHandler if ($user['type'] !== 'user') { throw new AssertionError("Can only create a secret chat with a user!"); } - $this->logger->logger('Creating secret chat with '.$user['user_id'].'...', Logger::VERBOSE); + $user = $user['user_id']; + $this->logger->logger('Creating secret chat with '.$user.'...', Logger::VERBOSE); $dh_config = ($this->getDhConfig()); $this->logger->logger('Generating a...', Logger::VERBOSE); $a = new BigInteger(Tools::random(256), 256); diff --git a/tests/faust.preview.jpg b/tests/faust.preview.jpg deleted file mode 100644 index 750e5ef96..000000000 Binary files a/tests/faust.preview.jpg and /dev/null differ