Repository: defstudio/telegraph (php)

Issue:
Implement editMessageMedia method
https://core.telegram.org/bots/api#editmessagemedia

Pull request description:
Issue #268 implement editMessageMedia method
close #268 implemented the method with relative docs and test (with snapshot). I need to know if the method input (the new media) must be a string or its better an object array and then convert it to json ( A JSON-serialized object is required in the Telegram Api). I'm gonna open a new issue for the Facade cache bug.  

Patch:
diff --git a/docs/content/en/features/attachments.md b/docs/content/en/features/attachments.md
index a38ff15d..2b218c36 100644
--- a/docs/content/en/features/attachments.md
+++ b/docs/content/en/features/attachments.md
@@ -21,6 +21,8 @@ Telegraph::photo('https://my-repository/photo.jpg')->send();
 Telegraph::photo($telegramFileId)->send();
 ```
 
+<alert type="alert">Sent Photos can be edited with the [editMedia](features/telegram-api-calls#editMedia) call</alert>
+
 
 ### Vocal Messages
 
@@ -43,6 +45,8 @@ Telegraph::document('https://my-repository/my_document.pdf')->send();
 Telegraph::document($telegramFileId)->send();
 ```
 
+<alert type="alert">Sent Documents can be edited with the [editMedia](features/telegram-api-calls#editMedia) call</alert>
+
 ### Location
 
 A location attachment can be sent through Telegraph `->location()` method:
diff --git a/docs/content/en/features/telegram-api-calls.md b/docs/content/en/features/telegram-api-calls.md
index 065a8509..b3ffb76c 100644
--- a/docs/content/en/features/telegram-api-calls.md
+++ b/docs/content/en/features/telegram-api-calls.md
@@ -138,6 +138,15 @@ edits an attachment caption
 Telegraph::editCaption($messageId)->markdownV2('new caption')->send();
 ```
 
+## editMedia
+
+edits a media messages with a new media content
+
+```php
+Telegraph::editMedia($messageId)->photo($path)->send();
+Telegraph::editMedia($messageId)->document($path)->send();
+```
+
 ## getWebhookDebugInfo
 
 retrieves webhook debug data for the active bot
diff --git a/docs/content/en/models/telegraph-chat.md b/docs/content/en/models/telegraph-chat.md
index d2dd33b5..5649cab4 100644
--- a/docs/content/en/models/telegraph-chat.md
+++ b/docs/content/en/models/telegraph-chat.md
@@ -122,6 +122,17 @@ Starts a `Telegraph` call to edit an attachment's caption
 $telegraphChat->editCaption($messageId)->message('new caption')->send();
 ```
 
+### `editMedia()`
+
+Starts a `Telegraph` call to edit a media messages with a new media content
+
+```php
+/** @var \DefStudio\Telegraph\Models\TelegraphChat $telegraphChat */
+
+$telegraphChat->editMedia($messageId)->photo($path)->send();
+$telegraphChat->editMedia($messageId)->document($path)->send();
+```
+
 ### `replaceKeyboard()`
 
 Starts a `Telegraph` call to replace a message keyboard (see [keyboards](features/keyboards) for details)
diff --git a/src/Concerns/SendsAttachments.php b/src/Concerns/SendsAttachments.php
index aa9522ae..c8b117b8 100644
--- a/src/Concerns/SendsAttachments.php
+++ b/src/Concerns/SendsAttachments.php
@@ -8,6 +8,7 @@ namespace DefStudio\Telegraph\Concerns;
 
 use DefStudio\Telegraph\DTO\Attachment;
 use DefStudio\Telegraph\Exceptions\FileException;
+use DefStudio\Telegraph\ScopedPayloads\TelegraphEditMediaPayload;
 use DefStudio\Telegraph\Telegraph;
 use Illuminate\Support\Facades\File;
 use Illuminate\Support\Str;
@@ -52,7 +53,19 @@ trait SendsAttachments
         return $telegraph;
     }
 
-    public function location(float $latitude, float $longitude): Telegraph
+    public function editMedia(int $messageId): TelegraphEditMediaPayload
+    {
+        $telegraph = clone $this;
+
+        $telegraph->endpoint = self::ENDPOINT_EDIT_MEDIA;
+
+        $telegraph->data['chat_id'] = $telegraph->getChat()->chat_id;
+        $telegraph->data['message_id'] = $messageId;
+
+        return TelegraphEditMediaPayload::makeFrom($telegraph);
+    }
+
+    public function location(float $latitude, float $longitude): self
     {
         $telegraph = clone $this;
 
@@ -64,7 +77,7 @@ trait SendsAttachments
         return $telegraph;
     }
 
-    public function voice(string $path, string $filename = null): Telegraph
+    public function voice(string $path, string $filename = null): self
     {
         $telegraph = clone $this;
 
@@ -84,7 +97,7 @@ trait SendsAttachments
         return $telegraph;
     }
 
-    public function document(string $path, string $filename = null): Telegraph
+    public function document(string $path, string $filename = null): self
     {
         $telegraph = clone $this;
 
@@ -93,23 +106,12 @@ trait SendsAttachments
         $telegraph->data['chat_id'] = $telegraph->getChat()->chat_id;
 
 
-        if (File::exists($path)) {
-            if (($size = $telegraph->fileSizeInMb($path)) > Telegraph::MAX_DOCUMENT_SIZE_IN_MB) {
-                throw FileException::documentSizeExceeded($size);
-            }
-
-            $telegraph->files->put('document', new Attachment($path, $filename));
-
-            return $telegraph;
-        }
-
-        $telegraph->data['document'] = $path;
-        $telegraph->data['caption'] ??= '';
+        $this->attachDocument($telegraph, $path, $filename);
 
         return $telegraph;
     }
 
-    public function withoutContentTypeDetection(): Telegraph
+    public function withoutContentTypeDetection(): self
     {
         $telegraph = clone $this;
 
@@ -118,7 +120,7 @@ trait SendsAttachments
         return $telegraph;
     }
 
-    public function thumbnail(string $path): Telegraph
+    public function thumbnail(string $path): self
     {
         $telegraph = clone $this;
 
@@ -150,7 +152,7 @@ trait SendsAttachments
         return $telegraph;
     }
 
-    public function photo(string $path, string $filename = null): Telegraph
+    public function photo(string $path, string $filename = null): self
     {
         $telegraph = clone $this;
 
@@ -158,29 +160,7 @@ trait SendsAttachments
 
         $telegraph->data['chat_id'] = $telegraph->getChat()->chat_id;
 
-        if (File::exists($path)) {
-            if (($size = $telegraph->fileSizeInMb($path)) > Telegraph::MAX_PHOTO_SIZE_IN_MB) {
-                throw FileException::photoSizeExceeded($size);
-            }
-
-            $height = $telegraph->imageHeight($path);
-            $width = $telegraph->imageWidth($path);
-
-            if (($totalLength = $height + $width) > Telegraph::MAX_PHOTO_HEIGHT_WIDTH_TOTAL) {
-                throw FileException::invalidPhotoSize($totalLength);
-            }
-
-            if (($ratio = $height / $width) > Telegraph::MAX_PHOTO_HEIGHT_WIDTH_RATIO || $ratio < (1 / Telegraph::MAX_PHOTO_HEIGHT_WIDTH_RATIO)) {
-                throw FileException::invalidPhotoRatio($ratio);
-            }
-
-            $telegraph->files->put('photo', new Attachment($path, $filename));
-
-            return $telegraph;
-        }
-
-        $telegraph->data['photo'] = $path;
-        $telegraph->data['caption'] ??= '';
+        $this->attachPhoto($telegraph, $path, $filename);
 
         return $telegraph;
     }
@@ -224,7 +204,7 @@ trait SendsAttachments
         return ceil($sizeInKBytes * 100) / 100;
     }
 
-    public function dice(string $emoji = null): Telegraph
+    public function dice(string $emoji = null): self
     {
         $telegraph = clone $this;
 
@@ -237,4 +217,43 @@ trait SendsAttachments
 
         return $telegraph;
     }
+
+    protected function attachPhoto(self $telegraph, string $path, ?string $filename): void
+    {
+        if (File::exists($path)) {
+            if (($size = $telegraph->fileSizeInMb($path)) > Telegraph::MAX_PHOTO_SIZE_IN_MB) {
+                throw FileException::photoSizeExceeded($size);
+            }
+
+            $height = $telegraph->imageHeight($path);
+            $width = $telegraph->imageWidth($path);
+
+            if (($totalLength = $height + $width) > Telegraph::MAX_PHOTO_HEIGHT_WIDTH_TOTAL) {
+                throw FileException::invalidPhotoSize($totalLength);
+            }
+
+            if (($ratio = $height / $width) > Telegraph::MAX_PHOTO_HEIGHT_WIDTH_RATIO || $ratio < (1 / Telegraph::MAX_PHOTO_HEIGHT_WIDTH_RATIO)) {
+                throw FileException::invalidPhotoRatio($ratio);
+            }
+
+            $telegraph->files->put('photo', new Attachment($path, $filename));
+        } else {
+            $telegraph->data['photo'] = $path;
+            $telegraph->data['caption'] ??= '';
+        }
+    }
+
+    protected function attachDocument(self $telegraph, string $path,  ?string $filename): void
+    {
+        if (File::exists($path)) {
+            if (($size = $telegraph->fileSizeInMb($path)) > Telegraph::MAX_DOCUMENT_SIZE_IN_MB) {
+                throw FileException::documentSizeExceeded($size);
+            }
+
+            $telegraph->files->put('document', new Attachment($path, $filename));
+        } else {
+            $telegraph->data['document'] = $path;
+            $telegraph->data['caption'] ??= '';
+        }
+    }
 }
diff --git a/src/Facades/Telegraph.php b/src/Facades/Telegraph.php
index c94daeea..cdc44c10 100644
--- a/src/Facades/Telegraph.php
+++ b/src/Facades/Telegraph.php
@@ -35,6 +35,7 @@ use Illuminate\Support\Facades\Facade;
  * @method static \DefStudio\Telegraph\Telegraph  unpinMessage(string $messageId)
  * @method static \DefStudio\Telegraph\Telegraph  unpinAllMessages()
  * @method static \DefStudio\Telegraph\Telegraph  editCaption(string $messageId)
+ * @method static \DefStudio\Telegraph\Telegraph  editMedia(string $messageId)
  * @method static \DefStudio\Telegraph\Telegraph  answerInlineQuery(string $inlineQueryID, array $results)
  * @method static \DefStudio\Telegraph\Telegraph  document(string $path, string $filename = null)
  * @method static \DefStudio\Telegraph\Telegraph  photo(string $path, string $filename = null)
diff --git a/src/Models/TelegraphChat.php b/src/Models/TelegraphChat.php
index ef71f266..3b222d92 100644
--- a/src/Models/TelegraphChat.php
+++ b/src/Models/TelegraphChat.php
@@ -141,6 +141,11 @@ class TelegraphChat extends Model implements Storable
         return TelegraphFacade::chat($this)->editCaption($messageId);
     }
 
+    public function editMedia(int $messageId): Telegraph
+    {
+        return TelegraphFacade::chat($this)->editMedia($messageId);
+    }
+
     public function deleteMessage(int $messageId): Telegraph
     {
         return TelegraphFacade::chat($this)->deleteMessage($messageId);
diff --git a/src/ScopedPayloads/TelegraphEditMediaPayload.php b/src/ScopedPayloads/TelegraphEditMediaPayload.php
new file mode 100644
index 00000000..8dae1082
--- /dev/null
+++ b/src/ScopedPayloads/TelegraphEditMediaPayload.php
@@ -0,0 +1,50 @@
+<?php
+
+/** @noinspection PhpUnhandledExceptionInspection */
+
+namespace DefStudio\Telegraph\ScopedPayloads;
+
+use DefStudio\Telegraph\Concerns\BuildsFromTelegraphClass;
+
+class TelegraphEditMediaPayload extends \DefStudio\Telegraph\Telegraph
+{
+    use BuildsFromTelegraphClass;
+
+    public function photo(string $path, string $filename = null): self
+    {
+        $telegraph = clone $this;
+
+        $this->attachPhoto($telegraph, $path, $filename);
+
+        $data = [
+            'type' => 'photo',
+            'media' => $telegraph->files->has('photo')
+                ? "attach://photo"
+                : $telegraph->data['photo'],
+        ];
+
+
+        $telegraph->data['media'] = json_encode($data);
+
+        return $telegraph;
+    }
+
+    public function document(string $path, string $filename = null): self
+    {
+        $telegraph = clone $this;
+
+        $this->attachDocument($telegraph, $path, $filename);
+
+        $data = [
+            'type' => 'document',
+            'media' => $telegraph->files->has('document')
+                ? "attach://document"
+                : $telegraph->data['document'],
+        ];
+
+
+        $telegraph->data['media'] = json_encode($data);
+
+        return $telegraph;
+    }
+}
diff --git a/src/Telegraph.php b/src/Telegraph.php
index 10fdf6ba..a2bd90a2 100755
--- a/src/Telegraph.php
+++ b/src/Telegraph.php
@@ -70,6 +70,7 @@ class Telegraph
     public const ENDPOINT_UNPIN_ALL_MESSAGES = 'unpinAllChatMessages';
     public const ENDPOINT_EDIT_MESSAGE = 'editMessageText';
     public const ENDPOINT_EDIT_CAPTION = 'editMessageCaption';
+    public const ENDPOINT_EDIT_MEDIA = 'editMessageMedia';
     public const ENDPOINT_SEND_LOCATION = 'sendLocation';
     public const ENDPOINT_SEND_VOICE = 'sendVoice';
     public const ENDPOINT_SEND_CHAT_ACTION = 'sendChatAction';


Test patch:
diff --git a/docs/content/en/testing/assertions.md b/docs/content/en/testing/assertions.md
index bf02b00b..f40c5c51 100644
--- a/docs/content/en/testing/assertions.md
+++ b/docs/content/en/testing/assertions.md
@@ -59,6 +59,14 @@ Telegraph::assertSentFiles(\DefStudio\Telegraph\Telegraph::ENDPOINT_SEND_DOCUMEN
 ]);
 ```
 
+## assertSentEditMedia
+
+asserts that the given data was sent to a Telegram API endpoint
+
+```php
+Telegraph::assertSentEditMedia('mediaType', Storage::path('photo.jpg'));
+```
+
 ## assertStoredFile
 
 asserts that the given incoming file was stored 
diff --git a/src/Support/Testing/Fakes/TelegraphEditMediaFake.php b/src/Support/Testing/Fakes/TelegraphEditMediaFake.php
new file mode 100644
index 00000000..8a0d6284
--- /dev/null
+++ b/src/Support/Testing/Fakes/TelegraphEditMediaFake.php
@@ -0,0 +1,38 @@
+<?php
+
+/** @noinspection PhpUnhandledExceptionInspection */
+
+/** @noinspection PhpUnused */
+
+/** @noinspection PhpPropertyOnlyWrittenInspection */
+
+namespace DefStudio\Telegraph\Support\Testing\Fakes;
+
+use DefStudio\Telegraph\Concerns\FakesRequests;
+
+use DefStudio\Telegraph\ScopedPayloads\TelegraphEditMediaPayload;
+use DefStudio\Telegraph\Telegraph;
+
+class TelegraphEditMediaFake extends TelegraphEditMediaPayload
+{
+    use FakesRequests;
+
+    /**
+     * @param array<string, array<mixed>> $replies
+     */
+    public function __construct(array $replies = [])
+    {
+        parent::__construct();
+        $this->replies = $replies;
+    }
+
+    public static function assertSentEditMedia(string $type, string $media): void
+    {
+        self::assertSentData(Telegraph::ENDPOINT_EDIT_MEDIA, [
+            "media" => json_encode([
+                'type' => $type,
+                'media' => $media,
+            ]),
+        ], false);
+    }
+}
diff --git a/src/Support/Testing/Fakes/TelegraphFake.php b/src/Support/Testing/Fakes/TelegraphFake.php
index 5889db8b..8409dcf0 100644
--- a/src/Support/Testing/Fakes/TelegraphFake.php
+++ b/src/Support/Testing/Fakes/TelegraphFake.php
@@ -29,6 +29,15 @@ class TelegraphFake extends Telegraph
         $this->replies = $replies;
     }
 
+    public function editMedia(int $messageId): TelegraphEditMediaFake
+    {
+        $fake = new TelegraphEditMediaFake($this->replies);
+        $fake->endpoint = self::ENDPOINT_EDIT_MEDIA;
+        $fake->data['message_id'] = $messageId;
+
+        return $fake;
+    }
+
     public function poll(string $question): TelegraphPollPayload
     {
         $fake = new TelegraphPollFake($this->replies);
diff --git a/tests/Unit/Concerns/SendsAttachmentsTest.php b/tests/Unit/Concerns/SendsAttachmentsTest.php
index 57c5aaca..8fd1b266 100644
--- a/tests/Unit/Concerns/SendsAttachmentsTest.php
+++ b/tests/Unit/Concerns/SendsAttachmentsTest.php
@@ -310,3 +310,17 @@ it('can edit a message caption', function () {
     expect(fn (Telegraph $telegraph) => $telegraph->editCaption(42)->message('foo'))
         ->toMatchTelegramSnapshot();
 });
+
+it('can edit a media messages with a photo', function () {
+    $photo_path = 'www.photoUrl.com';
+
+    expect(fn (Telegraph $telegraph) => $telegraph->editMedia(42)->photo($photo_path))
+        ->toMatchTelegramSnapshot();
+});
+
+it('can edit a media messages with a document', function () {
+    $document_path = 'www.documentUrl.com';
+
+    expect(fn (Telegraph $telegraph) => $telegraph->editMedia(42)->document($document_path))
+        ->toMatchTelegramSnapshot();
+});
diff --git a/tests/Unit/Models/TelegraphChatTest.php b/tests/Unit/Models/TelegraphChatTest.php
index e0de4940..befd365e 100644
--- a/tests/Unit/Models/TelegraphChatTest.php
+++ b/tests/Unit/Models/TelegraphChatTest.php
@@ -10,6 +10,7 @@ use DefStudio\Telegraph\Facades\Telegraph;
 use DefStudio\Telegraph\Keyboard\Button;
 use DefStudio\Telegraph\Keyboard\Keyboard;
 use DefStudio\Telegraph\Models\TelegraphBot;
+use DefStudio\Telegraph\Support\Testing\Fakes\TelegraphEditMediaFake;
 use DefStudio\Telegraph\Support\Testing\Fakes\TelegraphPollFake;
 use DefStudio\Telegraph\Support\Testing\Fakes\TelegraphQuizFake;
 use Illuminate\Support\Facades\Storage;
@@ -220,6 +221,24 @@ it('can edit a message caption', function () {
     ], false);
 });
 
+it('can edit a media messages with a photo', function () {
+    Telegraph::fake();
+    $chat = make_chat();
+
+    $chat->editMedia(42)->photo('www.newMediaUrl.com')->send();
+
+    TelegraphEditMediaFake::assertSentEditMedia('photo', 'www.newMediaUrl.com');
+});
+
+it('can edit a media messages with a document', function () {
+    Telegraph::fake();
+    $chat = make_chat();
+
+    $chat->editMedia(42)->document('www.newMediaUrl.com')->send();
+
+    TelegraphEditMediaFake::assertSentEditMedia('document', 'www.newMediaUrl.com');
+});
+
 it('can delete a message', function () {
     Telegraph::fake();
     $chat = make_chat();
diff --git a/tests/__snapshots__/SendsAttachmentsTest__it_can_edit_a_media_messages__1.yml b/tests/__snapshots__/SendsAttachmentsTest__it_can_edit_a_media_messages__1.yml
new file mode 100644
index 00000000..8752522c
--- /dev/null
+++ b/tests/__snapshots__/SendsAttachmentsTest__it_can_edit_a_media_messages__1.yml
@@ -0,0 +1,6 @@
+url: 'https://api.telegram.org/bot3f3814e1-5836-3d77-904e-60f64b15df36/editMessageMedia'
+payload:
+    chat_id: '-123456789'
+    message_id: 42
+    media: '{"media":"www.mediaUrl.com","type":"photo"}'
+files: {  }
diff --git a/tests/__snapshots__/SendsAttachmentsTest__it_can_edit_a_media_messages_with_a_document__1.yml b/tests/__snapshots__/SendsAttachmentsTest__it_can_edit_a_media_messages_with_a_document__1.yml
new file mode 100644
index 00000000..b065c2ea
--- /dev/null
+++ b/tests/__snapshots__/SendsAttachmentsTest__it_can_edit_a_media_messages_with_a_document__1.yml
@@ -0,0 +1,8 @@
+url: 'https://api.telegram.org/bot3f3814e1-5836-3d77-904e-60f64b15df36/editMessageMedia'
+payload:
+    chat_id: '-123456789'
+    message_id: 42
+    document: www.documentUrl.com
+    caption: ''
+    media: '{"type":"document","media":"www.documentUrl.com"}'
+files: {  }
diff --git a/tests/__snapshots__/SendsAttachmentsTest__it_can_edit_a_media_messages_with_a_photo__1.yml b/tests/__snapshots__/SendsAttachmentsTest__it_can_edit_a_media_messages_with_a_photo__1.yml
new file mode 100644
index 00000000..bc7223f3
--- /dev/null
+++ b/tests/__snapshots__/SendsAttachmentsTest__it_can_edit_a_media_messages_with_a_photo__1.yml
@@ -0,0 +1,8 @@
+url: 'https://api.telegram.org/bot3f3814e1-5836-3d77-904e-60f64b15df36/editMessageMedia'
+payload:
+    chat_id: '-123456789'
+    message_id: 42
+    photo: www.photoUrl.com
+    caption: ''
+    media: '{"type":"photo","media":"www.photoUrl.com"}'
+files: {  }