diff --git a/src/InternalDoc.php b/src/InternalDoc.php index bb0d4cced..ffed666fb 100644 --- a/src/InternalDoc.php +++ b/src/InternalDoc.php @@ -1740,6 +1740,164 @@ abstract class InternalDoc { return $this->wrapper->getAPI()->sendPhoto($peer, $file, $caption, $parseMode, $callback, $fileName, $ttl, $spoiler, $replyToMsgId, $topMsgId, $replyMarkup, $sendAs, $scheduleDate, $silent, $noForwards, $background, $clearDraft, $updateStickersetsOrder, $forceResend, $cancellation); } + /** + * Sends a sticker. + * + * Please use named arguments to call this method. + * + * @param integer|string $peer Destination peer or username. + * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message. + * @param ?callable(float, float, int) $callback Upload callback (percent, speed in mpbs, time elapsed) + * @param ?string $fileName Optional file name, if absent will be extracted from the passed $file. + * @param integer|null $replyToMsgId ID of message to reply to. + * @param integer|null $topMsgId ID of thread where to send the message. + * @param array|null $replyMarkup Keyboard information. + * @param integer|null $sendAs Peer to send the message as. + * @param integer|null $scheduleDate Schedule date. + * @param boolean $silent Whether to send the message silently, without triggering notifications. + * @param boolean $noForwards Whether to disable forwards for this message. + * @param boolean $background Send this message as background message + * @param boolean $clearDraft Clears the draft field + * @param boolean $updateStickersetsOrder Whether to move used stickersets to top + * @param boolean $forceResend Whether to forcefully resend the file, even if its type and name are the same. + * @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, ?callable $callback = null, ?string $fileName = 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, $callback, $fileName, $replyToMsgId, $topMsgId, $replyMarkup, $sendAs, $scheduleDate, $silent, $noForwards, $background, $clearDraft, $updateStickersetsOrder, $forceResend, $cancellation); + } + /** + * Sends a video. + * + * Please use named arguments to call this method. + * + * @param integer|string $peer Destination peer or username. + * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message. + * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream|null $thumb Optional: Thumbnail to upload + * @param string $caption Caption of document + * @param ParseMode $parseMode Text parse mode for the caption + * @param ?callable(float, float, int) $callback Upload callback (percent, speed in mpbs, time elapsed) + * @param ?string $fileName Optional file name, if absent will be extracted from the passed $file. + * @param integer|null $ttl Time to live + * @param boolean $spoiler Whether the message is a spoiler + * @param boolean $roundMessage Whether the message should be round + * @param boolean $supportsStreaming Whether the video supports streaming + * @param boolean $noSound Whether the video has no sound + * @param integer|null $duration Duration of the video + * @param integer|null $width Width of the video + * @param integer|null $height Height of the video + * @param integer|null $replyToMsgId ID of message to reply to. + * @param integer|null $topMsgId ID of thread where to send the message. + * @param array|null $replyMarkup Keyboard information. + * @param integer|string|null $sendAs Peer to send the message as. + * @param integer|null $scheduleDate Schedule date. + * @param boolean $silent Whether to send the message silently, without triggering notifications. + * @param boolean $noForwards Whether to disable forwards for this message. + * @param boolean $background Send this message as background message + * @param boolean $clearDraft Clears the draft field + * @param boolean $forceResend Whether to forcefully resend the file, even if its type and name are the same. + * @param Cancellation $cancellation Cancellation. + * + */ + final public function sendVideo(string|int $peer, \danog\MadelineProto\EventHandler\Message|\danog\MadelineProto\EventHandler\Media|\danog\MadelineProto\LocalFile|\danog\MadelineProto\RemoteUrl|\danog\MadelineProto\BotApiFileId|\Amp\ByteStream\ReadableStream $file, \danog\MadelineProto\EventHandler\Message|\danog\MadelineProto\EventHandler\Media|\danog\MadelineProto\LocalFile|\danog\MadelineProto\RemoteUrl|\danog\MadelineProto\BotApiFileId|\Amp\ByteStream\ReadableStream|null $thumb = null, string $caption = '', \danog\MadelineProto\ParseMode $parseMode = \danog\MadelineProto\ParseMode::TEXT, ?callable $callback = null, ?string $fileName = null, ?int $ttl = null, bool $spoiler = false, bool $roundMessage = false, bool $supportsStreaming = false, bool $noSound = false, ?int $duration = null, ?int $width = null, ?int $height = 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 $forceResend = false, ?\Amp\Cancellation $cancellation = null): \danog\MadelineProto\EventHandler\Message + { + return $this->wrapper->getAPI()->sendVideo($peer, $file, $thumb, $caption, $parseMode, $callback, $fileName, $ttl, $spoiler, $roundMessage, $supportsStreaming, $noSound, $duration, $width, $height, $replyToMsgId, $topMsgId, $replyMarkup, $sendAs, $scheduleDate, $silent, $noForwards, $background, $clearDraft, $forceResend, $cancellation); + } + /** + * Sends a gif. + * + * Please use named arguments to call this method. + * + * @param integer|string $peer Destination peer or username. + * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message. + * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream|null $thumb Optional: Thumbnail to upload + * @param string $caption Caption of document + * @param ParseMode $parseMode Text parse mode for the caption + * @param ?callable(float, float, int) $callback Upload callback (percent, speed in mpbs, time elapsed) + * @param ?string $fileName Optional file name, if absent will be extracted from the passed $file. + * @param integer|null $ttl Time to live + * @param boolean $spoiler Whether the message is a spoiler + * @param integer|null $replyToMsgId ID of message to reply to. + * @param integer|null $topMsgId ID of thread where to send the message. + * @param array|null $replyMarkup Keyboard information. + * @param integer|string|null $sendAs Peer to send the message as. + * @param integer|null $scheduleDate Schedule date. + * @param boolean $silent Whether to send the message silently, without triggering notifications. + * @param boolean $noForwards Whether to disable forwards for this message. + * @param boolean $background Send this message as background message + * @param boolean $clearDraft Clears the draft field + * @param boolean $forceResend Whether to forcefully resend the file, even if its type and name are the same. + * @param ?Cancellation $cancellation Cancellation. + * + */ + final public function sendGif(string|int $peer, \danog\MadelineProto\EventHandler\Message|\danog\MadelineProto\EventHandler\Media|\danog\MadelineProto\LocalFile|\danog\MadelineProto\RemoteUrl|\danog\MadelineProto\BotApiFileId|\Amp\ByteStream\ReadableStream $file, \danog\MadelineProto\EventHandler\Message|\danog\MadelineProto\EventHandler\Media|\danog\MadelineProto\LocalFile|\danog\MadelineProto\RemoteUrl|\danog\MadelineProto\BotApiFileId|\Amp\ByteStream\ReadableStream|null $thumb = null, string $caption = '', \danog\MadelineProto\ParseMode $parseMode = \danog\MadelineProto\ParseMode::TEXT, ?callable $callback = null, ?string $fileName = null, ?int $ttl = null, bool $spoiler = false, ?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 $forceResend = false, ?\Amp\Cancellation $cancellation = null): \danog\MadelineProto\EventHandler\Message + { + return $this->wrapper->getAPI()->sendGif($peer, $file, $thumb, $caption, $parseMode, $callback, $fileName, $ttl, $spoiler, $replyToMsgId, $topMsgId, $replyMarkup, $sendAs, $scheduleDate, $silent, $noForwards, $background, $clearDraft, $forceResend, $cancellation); + } + /** + * Sends an audio. + * + * Please use named arguments to call this method. + * + * @param integer|string $peer Destination peer or username. + * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message. + * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream|null $thumb Optional: Thumbnail to upload + * @param string $caption Caption of document + * @param ParseMode $parseMode Text parse mode for the caption + * @param ?callable(float, float, int) $callback Upload callback (percent, speed in mpbs, time elapsed) + * @param ?string $fileName Optional file name, if absent will be extracted from the passed $file. + * @param integer|null $duration Duration of the audio + * @param string|null $title Title of the audio + * @param string|null $performer Performer of the audio + * @param integer|null $replyToMsgId ID of message to reply to. + * @param integer|null $topMsgId ID of thread where to send the message. + * @param array|null $replyMarkup Keyboard information. + * @param integer|string|null $sendAs Peer to send the message as. + * @param integer|null $scheduleDate Schedule date. + * @param boolean $silent Whether to send the message silently, without triggering notifications. + * @param boolean $noForwards Whether to disable forwards for this message. + * @param boolean $background Send this message as background message + * @param boolean $clearDraft Clears the draft field + * @param boolean $forceResend Whether to forcefully resend the file, even if its type and name are the same. + * @param ?Cancellation $cancellation Cancellation. + * + */ + final public function sendAudio(string|int $peer, \danog\MadelineProto\EventHandler\Message|\danog\MadelineProto\EventHandler\Media|\danog\MadelineProto\LocalFile|\danog\MadelineProto\RemoteUrl|\danog\MadelineProto\BotApiFileId|\Amp\ByteStream\ReadableStream $file, \danog\MadelineProto\EventHandler\Message|\danog\MadelineProto\EventHandler\Media|\danog\MadelineProto\LocalFile|\danog\MadelineProto\RemoteUrl|\danog\MadelineProto\BotApiFileId|\Amp\ByteStream\ReadableStream|null $thumb = null, string $caption = '', \danog\MadelineProto\ParseMode $parseMode = \danog\MadelineProto\ParseMode::TEXT, ?callable $callback = null, ?string $fileName = null, ?int $duration = null, ?string $title = null, ?string $performer = 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 $forceResend = false, ?\Amp\Cancellation $cancellation = null): \danog\MadelineProto\EventHandler\Message + { + return $this->wrapper->getAPI()->sendAudio($peer, $file, $thumb, $caption, $parseMode, $callback, $fileName, $duration, $title, $performer, $replyToMsgId, $topMsgId, $replyMarkup, $sendAs, $scheduleDate, $silent, $noForwards, $background, $clearDraft, $forceResend, $cancellation); + } + /** + * Sends a voice. + * + * Please use named arguments to call this method. + * + * @param integer|string $peer Destination peer or username. + * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message. + * @param string $caption Caption of document + * @param ParseMode $parseMode Text parse mode for the caption + * @param ?callable(float, float, int) $callback Upload callback (percent, speed in mpbs, time elapsed) + * @param ?string $fileName Optional file name, if absent will be extracted from the passed $file. + * @param integer|null $ttl Time to live + * @param integer|null $duration Duration of the voice + * @param array|null $waveform Waveform of the voice + * @param integer|null $replyToMsgId ID of message to reply to. + * @param integer|null $topMsgId ID of thread where to send the message. + * @param array|null $replyMarkup Keyboard information. + * @param integer|string|null $sendAs Peer to send the message as. + * @param integer|null $scheduleDate Schedule date. + * @param boolean $silent Whether to send the message silently, without triggering notifications. + * @param boolean $noForwards Whether to disable forwards for this message. + * @param boolean $background Send this message as background message + * @param boolean $clearDraft Clears the draft field + * @param boolean $forceResend Whether to forcefully resend the file, even if its type and name are the same. + * @param ?Cancellation $cancellation Cancellation. + * + */ + final public function sendVoice(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 $caption = '', \danog\MadelineProto\ParseMode $parseMode = \danog\MadelineProto\ParseMode::TEXT, ?callable $callback = null, ?string $fileName = null, ?int $ttl = null, ?int $duration = null, ?array $waveform = 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 $forceResend = false, ?\Amp\Cancellation $cancellation = null): \danog\MadelineProto\EventHandler\Message + { + return $this->wrapper->getAPI()->sendVoice($peer, $file, $caption, $parseMode, $callback, $fileName, $ttl, $duration, $waveform, $replyToMsgId, $topMsgId, $replyMarkup, $sendAs, $scheduleDate, $silent, $noForwards, $background, $clearDraft, $forceResend, $cancellation); + } /** * Set NOOP update handler, ignoring all updates. */ diff --git a/src/MTProtoTools/FilesAbstraction.php b/src/MTProtoTools/FilesAbstraction.php index 566a9a5c8..615fe7c4a 100644 --- a/src/MTProtoTools/FilesAbstraction.php +++ b/src/MTProtoTools/FilesAbstraction.php @@ -23,11 +23,17 @@ namespace danog\MadelineProto\MTProtoTools; use Amp\ByteStream\ReadableBuffer; use Amp\ByteStream\ReadableStream; use Amp\Cancellation; +use Amp\Process\Process; use AssertionError; use danog\MadelineProto\BotApiFileId; use danog\MadelineProto\EventHandler\Media; +use danog\MadelineProto\EventHandler\Media\Audio; use danog\MadelineProto\EventHandler\Media\Document; +use danog\MadelineProto\EventHandler\Media\Gif; use danog\MadelineProto\EventHandler\Media\Photo; +use danog\MadelineProto\EventHandler\Media\Sticker; +use danog\MadelineProto\EventHandler\Media\Video; +use danog\MadelineProto\EventHandler\Media\Voice; use danog\MadelineProto\EventHandler\Message; use danog\MadelineProto\Exception; use danog\MadelineProto\LocalFile; @@ -37,7 +43,9 @@ use danog\MadelineProto\Settings; use danog\MadelineProto\TL\Types\Bytes; use Webmozart\Assert\Assert; +use function Amp\async; use function Amp\ByteStream\buffer; +use function Amp\ByteStream\pipe; /** * Manages upload and download of files. @@ -108,6 +116,7 @@ trait FilesAbstraction type: Document::class, mimeType: $mimeType, thumb: $thumb, + attributes: [], peer: $peer, file: $file, caption: $caption, @@ -187,6 +196,7 @@ trait FilesAbstraction type: Photo::class, mimeType: 'image/jpeg', thumb: null, + attributes: [], peer: $peer, file: $file, caption: $caption, @@ -209,7 +219,426 @@ trait FilesAbstraction cancellation: $cancellation ); } + /** + * Sends a sticker. + * + * Please use named arguments to call this method. + * + * @param integer|string $peer Destination peer or username. + * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message. + * @param ?callable(float, float, int) $callback Upload callback (percent, speed in mpbs, time elapsed) + * @param ?string $fileName Optional file name, if absent will be extracted from the passed $file. + * @param integer|null $replyToMsgId ID of message to reply to. + * @param integer|null $topMsgId ID of thread where to send the message. + * @param array|null $replyMarkup Keyboard information. + * @param integer|null $sendAs Peer to send the message as. + * @param integer|null $scheduleDate Schedule date. + * @param boolean $silent Whether to send the message silently, without triggering notifications. + * @param boolean $noForwards Whether to disable forwards for this message. + * @param boolean $background Send this message as background message + * @param boolean $clearDraft Clears the draft field + * @param boolean $updateStickersetsOrder Whether to move used stickersets to top + * @param boolean $forceResend Whether to forcefully resend the file, even if its type and name are the same. + * @param Cancellation $cancellation Cancellation. + * + */ + public function sendSticker( + int|string $peer, + Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file, + ?callable $callback = null, + ?string $fileName = null, + ?int $replyToMsgId = null, + ?int $topMsgId = null, + ?array $replyMarkup = null, + int|string|null $sendAs = null, + ?int $scheduleDate = null, + bool $silent = false, + bool $noForwards = false, + bool $background = false, + bool $clearDraft = false, + bool $updateStickersetsOrder = false, + bool $forceResend = false, + ?Cancellation $cancellation = null, + ): Message { + if ($file instanceof Message) { + $file = $file->media; + if ($file === null) { + throw new AssertionError("The message must be a media message!"); + } + } + return $this->sendMedia( + type: Sticker::class, + mimeType: 'image/webp', + thumb: null, + attributes: [], + peer: $peer, + file: $file, + caption: '', + parseMode: ParseMode::TEXT, + callback: $callback, + fileName: $fileName, + ttl: null, + spoiler: false, + silent: $silent, + background: $background, + clearDraft: $clearDraft, + noForwards: $noForwards, + updateStickersetsOrder: $updateStickersetsOrder, + replyToMsgId: $replyToMsgId, + topMsgId: $topMsgId, + replyMarkup: $replyMarkup, + scheduleDate: $scheduleDate, + sendAs: $sendAs, + forceResend: $forceResend, + cancellation: $cancellation + ); + } + /** + * Sends a video. + * + * Please use named arguments to call this method. + * + * @param integer|string $peer Destination peer or username. + * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message. + * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream|null $thumb Optional: Thumbnail to upload + * @param string $caption Caption of document + * @param ParseMode $parseMode Text parse mode for the caption + * @param ?callable(float, float, int) $callback Upload callback (percent, speed in mpbs, time elapsed) + * @param ?string $fileName Optional file name, if absent will be extracted from the passed $file. + * @param integer|null $ttl Time to live + * @param boolean $spoiler Whether the message is a spoiler + * @param boolean $roundMessage Whether the message should be round + * @param boolean $supportsStreaming Whether the video supports streaming + * @param boolean $noSound Whether the video has no sound + * @param integer|null $duration Duration of the video + * @param integer|null $width Width of the video + * @param integer|null $height Height of the video + * @param integer|null $replyToMsgId ID of message to reply to. + * @param integer|null $topMsgId ID of thread where to send the message. + * @param array|null $replyMarkup Keyboard information. + * @param integer|string|null $sendAs Peer to send the message as. + * @param integer|null $scheduleDate Schedule date. + * @param boolean $silent Whether to send the message silently, without triggering notifications. + * @param boolean $noForwards Whether to disable forwards for this message. + * @param boolean $background Send this message as background message + * @param boolean $clearDraft Clears the draft field + * @param boolean $forceResend Whether to forcefully resend the file, even if its type and name are the same. + * @param Cancellation $cancellation Cancellation. + * + */ + public function sendVideo( + int|string $peer, + Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file, + Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream|null $thumb = null, + string $caption = '', + ParseMode $parseMode = ParseMode::TEXT, + ?callable $callback = null, + ?string $fileName = null, + ?int $ttl = null, + bool $spoiler = false, + bool $roundMessage = false, + bool $supportsStreaming = true, + bool $noSound = false, + ?int $duration = null, + ?int $width = null, + ?int $height = null, + ?int $replyToMsgId = null, + ?int $topMsgId = null, + ?array $replyMarkup = null, + int|string|null $sendAs = null, + ?int $scheduleDate = null, + bool $silent = false, + bool $noForwards = false, + bool $background = false, + bool $clearDraft = false, + bool $forceResend = false, + ?Cancellation $cancellation = null, + ): Message { + if ($file instanceof Message) { + $file = $file->media; + if ($file === null) { + throw new AssertionError("The message must be a media message!"); + } + } + + $attributes = [ + 'round_message' => $roundMessage, + 'supports_streaming' => $supportsStreaming, + 'no_sound' => $noSound, + 'duration' => $duration, + 'w' => $width, + 'h' => $height, + ]; + + return $this->sendMedia( + type: Video::class, + mimeType: 'video/mp4', + thumb: $thumb, + attributes: $attributes, + peer: $peer, + file: $file, + caption: $caption, + parseMode: $parseMode, + callback: $callback, + fileName: $fileName, + ttl: $ttl, + spoiler: $spoiler, + silent: $silent, + background: $background, + clearDraft: $clearDraft, + noForwards: $noForwards, + updateStickersetsOrder: false, + replyToMsgId: $replyToMsgId, + topMsgId: $topMsgId, + replyMarkup: $replyMarkup, + scheduleDate: $scheduleDate, + sendAs: $sendAs, + forceResend: $forceResend, + cancellation: $cancellation + ); + } + /** + * Sends a gif. + * + * Please use named arguments to call this method. + * + * @param integer|string $peer Destination peer or username. + * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message. + * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream|null $thumb Optional: Thumbnail to upload + * @param string $caption Caption of document + * @param ParseMode $parseMode Text parse mode for the caption + * @param ?callable(float, float, int) $callback Upload callback (percent, speed in mpbs, time elapsed) + * @param ?string $fileName Optional file name, if absent will be extracted from the passed $file. + * @param integer|null $ttl Time to live + * @param boolean $spoiler Whether the message is a spoiler + * @param integer|null $replyToMsgId ID of message to reply to. + * @param integer|null $topMsgId ID of thread where to send the message. + * @param array|null $replyMarkup Keyboard information. + * @param integer|string|null $sendAs Peer to send the message as. + * @param integer|null $scheduleDate Schedule date. + * @param boolean $silent Whether to send the message silently, without triggering notifications. + * @param boolean $noForwards Whether to disable forwards for this message. + * @param boolean $background Send this message as background message + * @param boolean $clearDraft Clears the draft field + * @param boolean $forceResend Whether to forcefully resend the file, even if its type and name are the same. + * @param ?Cancellation $cancellation Cancellation. + * + */ + public function sendGif( + int|string $peer, + Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file, + Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream|null $thumb = null, + string $caption = '', + ParseMode $parseMode = ParseMode::TEXT, + ?callable $callback = null, + ?string $fileName = null, + ?int $ttl = null, + bool $spoiler = false, + ?int $replyToMsgId = null, + ?int $topMsgId = null, + ?array $replyMarkup = null, + int|string|null $sendAs = null, + ?int $scheduleDate = null, + bool $silent = false, + bool $noForwards = false, + bool $background = false, + bool $clearDraft = false, + bool $forceResend = false, + ?Cancellation $cancellation = null, + ): Message { + return $this->sendMedia( + type: Gif::class, + mimeType: 'image/gif', + thumb: $thumb, + attributes: [], + peer: $peer, + file: $file, + caption: $caption, + parseMode: $parseMode, + callback: $callback, + fileName: $fileName, + ttl: $ttl, + spoiler: $spoiler, + silent: $silent, + background: $background, + clearDraft: $clearDraft, + noForwards: $noForwards, + updateStickersetsOrder: false, + replyToMsgId: $replyToMsgId, + topMsgId: $topMsgId, + replyMarkup: $replyMarkup, + scheduleDate: $scheduleDate, + sendAs: $sendAs, + forceResend: $forceResend, + cancellation: $cancellation + ); + } + /** + * Sends an audio. + * + * Please use named arguments to call this method. + * + * @param integer|string $peer Destination peer or username. + * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message. + * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream|null $thumb Optional: Thumbnail to upload + * @param string $caption Caption of document + * @param ParseMode $parseMode Text parse mode for the caption + * @param ?callable(float, float, int) $callback Upload callback (percent, speed in mpbs, time elapsed) + * @param ?string $fileName Optional file name, if absent will be extracted from the passed $file. + * @param integer|null $duration Duration of the audio + * @param string|null $title Title of the audio + * @param string|null $performer Performer of the audio + * @param integer|null $replyToMsgId ID of message to reply to. + * @param integer|null $topMsgId ID of thread where to send the message. + * @param array|null $replyMarkup Keyboard information. + * @param integer|string|null $sendAs Peer to send the message as. + * @param integer|null $scheduleDate Schedule date. + * @param boolean $silent Whether to send the message silently, without triggering notifications. + * @param boolean $noForwards Whether to disable forwards for this message. + * @param boolean $background Send this message as background message + * @param boolean $clearDraft Clears the draft field + * @param boolean $forceResend Whether to forcefully resend the file, even if its type and name are the same. + * @param ?Cancellation $cancellation Cancellation. + * + */ + public function sendAudio( + int|string $peer, + Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file, + Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream|null $thumb = null, + string $caption = '', + ParseMode $parseMode = ParseMode::TEXT, + ?callable $callback = null, + ?string $fileName = null, + ?int $duration = null, + ?string $title = null, + ?string $performer = null, + ?int $replyToMsgId = null, + ?int $topMsgId = null, + ?array $replyMarkup = null, + int|string|null $sendAs = null, + ?int $scheduleDate = null, + bool $silent = false, + bool $noForwards = false, + bool $background = false, + bool $clearDraft = false, + bool $forceResend = false, + ?Cancellation $cancellation = null, + ): Message { + $attributes = [ + 'duration' => $duration, + 'title' => $title, + 'performer' => $performer, + ]; + + return $this->sendMedia( + type: Audio::class, + mimeType: 'audio/mpeg', + thumb: $thumb, + attributes: $attributes, + peer: $peer, + file: $file, + caption: $caption, + parseMode: $parseMode, + callback: $callback, + fileName: $fileName, + ttl: null, + spoiler: null, + silent: $silent, + background: $background, + clearDraft: $clearDraft, + noForwards: $noForwards, + updateStickersetsOrder: false, + replyToMsgId: $replyToMsgId, + topMsgId: $topMsgId, + replyMarkup: $replyMarkup, + scheduleDate: $scheduleDate, + sendAs: $sendAs, + forceResend: $forceResend, + cancellation: $cancellation + ); + } + /** + * Sends a voice. + * + * Please use named arguments to call this method. + * + * @param integer|string $peer Destination peer or username. + * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message. + * @param string $caption Caption of document + * @param ParseMode $parseMode Text parse mode for the caption + * @param ?callable(float, float, int) $callback Upload callback (percent, speed in mpbs, time elapsed) + * @param ?string $fileName Optional file name, if absent will be extracted from the passed $file. + * @param integer|null $ttl Time to live + * @param integer|null $duration Duration of the voice + * @param array|null $waveform Waveform of the voice + * @param integer|null $replyToMsgId ID of message to reply to. + * @param integer|null $topMsgId ID of thread where to send the message. + * @param array|null $replyMarkup Keyboard information. + * @param integer|string|null $sendAs Peer to send the message as. + * @param integer|null $scheduleDate Schedule date. + * @param boolean $silent Whether to send the message silently, without triggering notifications. + * @param boolean $noForwards Whether to disable forwards for this message. + * @param boolean $background Send this message as background message + * @param boolean $clearDraft Clears the draft field + * @param boolean $forceResend Whether to forcefully resend the file, even if its type and name are the same. + * @param ?Cancellation $cancellation Cancellation. + * + */ + public function sendVoice( + int|string $peer, + Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file, + string $caption = '', + ParseMode $parseMode = ParseMode::TEXT, + ?callable $callback = null, + ?string $fileName = null, + ?int $ttl = null, + ?int $duration = null, + ?array $waveform = null, + ?int $replyToMsgId = null, + ?int $topMsgId = null, + ?array $replyMarkup = null, + int|string|null $sendAs = null, + ?int $scheduleDate = null, + bool $silent = false, + bool $noForwards = false, + bool $background = false, + bool $clearDraft = false, + bool $forceResend = false, + ?Cancellation $cancellation = null, + ): Message { + $attributes = [ + 'duration' => $duration, + 'waveform' => $waveform, + ]; + + return $this->sendMedia( + type: Voice::class, + mimeType: 'audio/ogg', + thumb: null, + attributes: $attributes, + peer: $peer, + file: $file, + caption: $caption, + parseMode: $parseMode, + callback: $callback, + fileName: $fileName, + ttl: $ttl, + spoiler: null, + silent: $silent, + background: $background, + clearDraft: $clearDraft, + noForwards: $noForwards, + updateStickersetsOrder: false, + replyToMsgId: $replyToMsgId, + topMsgId: $topMsgId, + replyMarkup: $replyMarkup, + scheduleDate: $scheduleDate, + sendAs: $sendAs, + forceResend: $forceResend, + cancellation: $cancellation + ); + } /** * Sends a media. * @@ -222,12 +651,13 @@ trait FilesAbstraction ?string $mimeType, Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file, Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream|null $thumb, + array $attributes, string $caption, ParseMode $parseMode, ?callable $callback, ?string $fileName, ?int $ttl, - bool $spoiler, + ?bool $spoiler, ?int $replyToMsgId, ?int $topMsgId, ?array $replyMarkup, @@ -278,6 +708,34 @@ trait FilesAbstraction } $attributes = match ($type) { + Video::class => [ + [ + '_' => 'documentAttributeVideo', + 'round_message' => $file->roundMessage ?? $attributes['round_message'], + 'supports_streaming' => $file->supportsStreaming ?? $attributes['supports_streaming'], + 'no_sound' => $file->noSound ?? $attributes['no_sound'], + 'duration' => $file->duration ?? $attributes['duration'], + 'w' => $file->width ?? $attributes['w'], + 'h' => $file->height ?? $attributes['h'], + ], + ], + Audio::class => [ + [ + '_' => 'documentAttributeAudio', + 'duration' => $file->duration ?? $attributes['duration'], + 'title' => $file->title ?? $attributes['title'], + 'performer' => $file->performer ?? $attributes['performer'], + ], + ], + Voice::class => [ + [ + '_' => 'documentAttributeAudio', + 'voice' => true, + 'duration' => $file->duration ?? $attributes['duration'], + 'waveform' => $file->waveform ?? $attributes['waveform'], + ], + ], + Gif::class => [['_' => 'documentAttributeAnimated']], default => [], }; $attributes[] = ['_' => 'documentAttributeFilename', 'file_name' => $fileName]; @@ -399,6 +857,80 @@ trait FilesAbstraction 'cancellation' => $cancellation, ]; } else { + + if ($reuseId) { + // Reuse + } elseif ($type === Video::class) { + if (Process::start('ffmpeg -version')->join() !== 0) { + $this->logger->logger('Install ffmpeg for video info extraction!'); + } elseif ($thumb === null || $attributes[0]['duration'] === null || $attributes[0]['w'] === null || $attributes[0]['h'] === null) { + $file = $this->getStream($file, $cancellation); + $ffmpeg = 'ffmpeg -i pipe: -ss 00:00:01.000 -frames:v 1 -f image2pipe -vcodec mjpeg pipe:1'; + $process = Process::start($ffmpeg); + async(fn () => pipe($file, $process->getStdin()))->finally(fn () => $process->getStdin()->close()); + $thumb ??= new ReadableBuffer(buffer($process->getStdout())); + $output = buffer($process->getStderr()); + if (preg_match('~Duration: (\d{2}:\d{2}:\d{2}\.\d{2}),.*? (\d{3,4})x(\d{3,4})~s', $output, $matches)) { + $time = explode(':', $matches[1]); + $hours = (int) $time[0]; + $minutes = (int) $time[1]; + $seconds = (int) $time[2]; + $duration = $hours * 3600 + $minutes * 60 + $seconds; + $width = $matches[2]; + $height = $matches[3]; + $attributes[0]['w'] ??= $width; + $attributes[0]['h'] ??= $height; + $attributes[0]['duration'] ??= $duration; + } + } + } elseif ($type === Sticker::class) { + if (!extension_loaded('gd')) { + throw Exception::extension('gd'); + } + $file = buffer($this->getStream($file, $cancellation), $cancellation); + $img = imagecreatefromstring($file); + $width = imagesx($img); + $height = imagesy($img); + if ($width > $height) { + $newWidth = 512; + $newHeight = (int) (512 * $height / $width); + } elseif ($width < $height) { + $newWidth = (int) (512 * $width / $height); + $newHeight = 512; + } else { + $newWidth = 512; + $newHeight = 512; + } + $temp = imagecreatetruecolor($newWidth, $newHeight); + imagecopyresized($temp, $img, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height); + $stream = fopen('php://memory', 'r+'); + imagewebp($temp, $stream); + rewind($stream); + $file = stream_get_contents($stream); + fclose($stream); + unset($stream); + unset($temp); + $file = new ReadableBuffer($file); + } elseif ($type === Audio::class or $type === Voice::class) { + if (Process::start('ffmpeg -version')->join() !== 0) { + $this->logger->logger('Install ffmpeg for audio info extraction!'); + } elseif ($attributes[0]['duration'] === null) { + $file = $this->getStream($file, $cancellation); + $ffmpeg = 'ffmpeg -i pipe: 2>&1'; + $process = Process::start($ffmpeg); + async(fn () => pipe($file, $process->getStdin())); + $output = buffer($process->getStdout()); + if (preg_match('~Duration: (\d{2}:\d{2}:\d{2}\.\d{2})~', $output, $matches)) { + $time = explode(':', $matches[1]); + $hours = (int) $time[0]; + $minutes = (int) $time[1]; + $seconds = (int) $time[2]; + $duration = $hours * 3600 + $minutes * 60 + $seconds; + $attributes[0]['duration'] = $duration; + } + } + } + $method = 'messages.sendMedia'; $media = match ($type) { Photo::class => [ @@ -407,6 +939,45 @@ trait FilesAbstraction 'file' => $file, 'ttl_seconds' => $ttl, ], + Sticker::class => [ + '_' => 'inputMediaUploadedDocument', + 'file' => $file, + 'mime_type' => $mimeType, + 'attributes' => $attributes, + ], + Video::class => [ + '_' => 'inputMediaUploadedDocument', + 'nosound_video' => $attributes[0]['no_sound'], + 'spoiler' => $spoiler, + 'ttl_seconds' => $ttl, + 'force_file' => false, + 'file' => $file, + 'thumb' => $thumb, + 'mime_type' => $mimeType, + 'attributes' => $attributes, + ], + Gif::class => [ + '_' => 'inputMediaUploadedDocument', + 'spoiler' => $spoiler, + 'ttl_seconds' => $ttl, + 'file' => $file, + 'thumb' => $thumb, + 'mime_type' => $mimeType, + 'attributes' => $attributes, + ], + Audio::class => [ + '_' => 'inputMediaUploadedDocument', + 'file' => $file, + 'thumb' => $thumb, + 'mime_type' => $mimeType, + 'attributes' => $attributes, + ], + Voice::class => [ + '_' => 'inputMediaUploadedDocument', + 'file' => $file, + 'mime_type' => $mimeType, + 'attributes' => $attributes, + ], default => [ '_' => 'inputMediaUploadedDocument', 'spoiler' => $spoiler, @@ -421,7 +992,7 @@ trait FilesAbstraction if ($reuseId) { $media['_'] = match ($type) { Photo::class => 'inputMediaPhoto', - Document::class => 'inputMediaDocument', + default => 'inputMediaDocument', }; $media['id'] = $reuseId; } else {