Repository: def-studio/telegraph (php)

Issue:
[FEAT] Add editMessageCaption endpoint
Hello everyone,
Thanks a lot for your superb work, this package is amazing.

I just wanted to ask for an enhancement: the `edit()` function provided by the `TelegraphChat` model doesn't work for messages with files. To make it work, I followed these steps (it's working, but I'm not sure if this is the best way to do this):

- added the `editCaption()` function inside the `TelegraphChat` model
- added the `editCaption()` function inside the `ComposesMessages` trait
- added the `ENDPOINT_EDIT_CAPTION` const inside the `Telegraph` class
- updated the `setMessageText()` function inside the `ComposesMessages` trait, adding the line `$this->data['caption'] = $message;` between lines 25 and 26

This works for me, but I think it would be better this way (just as suggestion):
- add a parameter to the `edit()` function -> `edit($messageId, $isCaption = false)`
- add a parameter to the `setMessageText()` function (and related handler) -> `setMessageText($message, $isCaption = false)`

or maybe it would be possible to just add a function that changes the parameter if the message has files attached

Reference:
https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1edit_message_caption.html


Thanks a lot in advance!

Pull request description:
add editCaption feature
implements editCaption call and closes #96 

Patch:
diff --git a/docs/content/en/features/attachments.md b/docs/content/en/features/attachments.md
index ebc8e120..6fbecbb0 100644
--- a/docs/content/en/features/attachments.md
+++ b/docs/content/en/features/attachments.md
@@ -65,6 +65,9 @@ Telegraph::document(Storage::path('my_document.pdf'))
     ->send();
 ```
 
+<alert type="alert">Sent attachment captions can be edited with the [editCaption](features/telegram-api-calls#editCaption) call</alert>
+
+
 ### Without notification
 
 ```php
diff --git a/docs/content/en/features/telegram-api-calls.md b/docs/content/en/features/telegram-api-calls.md
index 0ae6fc76..d7d1ad5a 100644
--- a/docs/content/en/features/telegram-api-calls.md
+++ b/docs/content/en/features/telegram-api-calls.md
@@ -91,6 +91,22 @@ sends a document
 Telegram::document($documentPath)->send();
 ```
 
+## edit
+
+edits a message
+
+```php
+Telegram::edit($messageId)->markdown('new message')->send();
+```
+
+## editCaption
+
+edits an attachment caption
+
+```php
+Telegram::editCaption($messageId)->markdown('new caption')->send();
+```
+
 ## getWebhookDebugInfo
 
 retrieves webhook debug data for the active bot
@@ -105,7 +121,7 @@ sends a location attachment
 
 ```php
 Telegraph::location(12.345, -54.321)->send();
-``
+```
 
 ## markdown
 
diff --git a/docs/content/en/models/telegraph-chat.md b/docs/content/en/models/telegraph-chat.md
index f92188f4..ca3172c7 100644
--- a/docs/content/en/models/telegraph-chat.md
+++ b/docs/content/en/models/telegraph-chat.md
@@ -93,7 +93,17 @@ Starts a `Telegraph` call to edit a message
 ```php
 /** @var \DefStudio\Telegraph\Models\TelegraphChat $telegraphChat */
 
-$telegraphChat->edit($messageId)->keyboard('new text')->send();
+$telegraphChat->edit($messageId)->message('new text')->send();
+```
+
+### `editCaption()`
+
+Starts a `Telegraph` call to edit an attachment's caption
+
+```php
+/** @var \DefStudio\Telegraph\Models\TelegraphChat $telegraphChat */
+
+$telegraphChat->editCaption($messageId)->message('new caption')->send();
 ```
 
 ### `replaceKeyboard()`
diff --git a/src/Concerns/CallTraitsMethods.php b/src/Concerns/CallTraitsMethods.php
new file mode 100644
index 00000000..5a05199f
--- /dev/null
+++ b/src/Concerns/CallTraitsMethods.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace DefStudio\Telegraph\Concerns;
+
+use ReflectionClass;
+
+trait CallTraitsMethods
+{
+    /**
+     * @template TValue
+     * @param string $methodName
+     * @param TValue $pipeable
+     * @param mixed ...$params
+     *
+     * @return TValue
+     */
+    protected function pipeTraits(string $methodName, mixed $pipeable, mixed ...$params): mixed
+    {
+        $reflection = new ReflectionClass(self::class);
+        foreach ($reflection->getTraitNames() as $trait) {
+            $trait = class_basename($trait);
+
+            $traitMethod = "$methodName$trait";
+
+            if (method_exists($this, $traitMethod)) {
+                $pipeable = $this->$traitMethod($pipeable, ...$params);
+            }
+        }
+
+        return $pipeable;
+    }
+}
diff --git a/src/Concerns/InteractsWithTelegram.php b/src/Concerns/InteractsWithTelegram.php
index 0154a55b..d25d66a5 100644
--- a/src/Concerns/InteractsWithTelegram.php
+++ b/src/Concerns/InteractsWithTelegram.php
@@ -48,10 +48,7 @@ trait InteractsWithTelegram
     {
         $data = $this->data;
 
-        if ($this->files->isNotEmpty() && !empty($data['text'])) {
-            $data['caption'] = $data['text'];
-            unset($data['text']);
-        }
+        $data = $this->pipeTraits('preprocessData', $data);
 
         if ($asMultipart) {
             $data = collect($data)
@@ -64,6 +61,7 @@ trait InteractsWithTelegram
                 })->toArray();
         }
 
+
         return $data;
     }
 
diff --git a/src/Concerns/SendsAttachments.php b/src/Concerns/SendsAttachments.php
index 5deebf9b..c81c9da9 100644
--- a/src/Concerns/SendsAttachments.php
+++ b/src/Concerns/SendsAttachments.php
@@ -17,6 +17,36 @@ use Illuminate\Support\Str;
  */
 trait SendsAttachments
 {
+    /**
+     * @param array<string, mixed> $data
+     *
+     * @return array<string, mixed>
+     */
+    public function preprocessDataSendsAttachments(array $data): array
+    {
+        if ($this->files->isNotEmpty() && !empty($data['text'])) {
+            $data['caption'] = $data['text'];
+            unset($data['text']);
+        }
+
+        if ($this->endpoint === self::ENDPOINT_EDIT_CAPTION) {
+            $data['caption'] = $data['text'] ?? '';
+            unset($data['text']);
+        }
+
+        return $data;
+    }
+
+    public function editCaption(int $messageId): Telegraph
+    {
+        $telegraph = clone $this;
+
+        $telegraph->endpoint = self::ENDPOINT_EDIT_CAPTION;
+        $telegraph->data['message_id'] = $messageId;
+
+        return $telegraph;
+    }
+
     public function location(float $latitude, float $longitude): Telegraph
     {
         $telegraph = clone $this;
diff --git a/src/Facades/Telegraph.php b/src/Facades/Telegraph.php
index 19e6c862..80bd5fc4 100644
--- a/src/Facades/Telegraph.php
+++ b/src/Facades/Telegraph.php
@@ -16,6 +16,7 @@ use Illuminate\Support\Facades\Facade;
  * @method static \DefStudio\Telegraph\Telegraph  message(string $message)
  * @method static \DefStudio\Telegraph\Telegraph  html(string $message)
  * @method static \DefStudio\Telegraph\Telegraph  reply(int $messageId)
+ * @method static \DefStudio\Telegraph\Telegraph  edit(string $messageId)
  * @method static \DefStudio\Telegraph\Telegraph  markdown(string $message)
  * @method static \DefStudio\Telegraph\Telegraph  registerWebhook()
  * @method static \DefStudio\Telegraph\Telegraph  unregisterWebhook(bool $dropPendingUpdates = false)
@@ -26,6 +27,7 @@ use Illuminate\Support\Facades\Facade;
  * @method static \DefStudio\Telegraph\Telegraph  replaceKeyboard(string $messageId, Keyboard|callable $newKeyboard)
  * @method static \DefStudio\Telegraph\Telegraph  deleteKeyboard(string $messageId)
  * @method static \DefStudio\Telegraph\Telegraph  deleteMessage(string $messageId)
+ * @method static \DefStudio\Telegraph\Telegraph  editCaption(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)
@@ -35,7 +37,7 @@ use Illuminate\Support\Facades\Facade;
  * @method static \DefStudio\Telegraph\Telegraph  botInfo()
  * @method static string store(Downloadable $attachment, string $path, string $filename = null)
  * @method static void  dumpSentData()
- * @method static void  assertSentData(string $endpoint, array $data = null)
+ * @method static void  assertSentData(string $endpoint, array $data = null, bool $exact = true)
  * @method static void  assertSentFiles(string $endpoint, array $files = null)
  * @method static void  assertSent(string $message, bool $exact = true)
  * @method static void  assertNothingSent()
diff --git a/src/Models/TelegraphChat.php b/src/Models/TelegraphChat.php
index 4661b5bf..60da199c 100644
--- a/src/Models/TelegraphChat.php
+++ b/src/Models/TelegraphChat.php
@@ -1,7 +1,7 @@
 <?php
 
+/** @noinspection PhpUnused */
 /** @noinspection PhpUnhandledExceptionInspection */
-
 /** @noinspection PhpDocMissingThrowsInspection */
 
 namespace DefStudio\Telegraph\Models;
@@ -93,6 +93,11 @@ class TelegraphChat extends Model
         return TelegraphFacade::chat($this)->edit($messageId);
     }
 
+    public function editCaption(int $messageId): Telegraph
+    {
+        return TelegraphFacade::chat($this)->editCaption($messageId);
+    }
+
     public function deleteMessage(int $messageId): Telegraph
     {
         return TelegraphFacade::chat($this)->deleteMessage($messageId);
diff --git a/src/Telegraph.php b/src/Telegraph.php
index 89f18ec8..6c24c8c9 100755
--- a/src/Telegraph.php
+++ b/src/Telegraph.php
@@ -9,6 +9,7 @@ namespace DefStudio\Telegraph;
 
 use DefStudio\Telegraph\Client\TelegraphResponse;
 use DefStudio\Telegraph\Concerns\AnswersInlineQueries;
+use DefStudio\Telegraph\Concerns\CallTraitsMethods;
 use DefStudio\Telegraph\Concerns\ComposesMessages;
 use DefStudio\Telegraph\Concerns\HasBotsAndChats;
 use DefStudio\Telegraph\Concerns\InteractsWithTelegram;
@@ -30,6 +31,7 @@ class Telegraph
     use SendsAttachments;
     use StoresFiles;
     use AnswersInlineQueries;
+    use CallTraitsMethods;
 
     public const MAX_DOCUMENT_SIZE_IN_MB = 50;
     public const MAX_PHOTO_SIZE_IN_MB = 10;
@@ -59,6 +61,7 @@ class Telegraph
     public const ENDPOINT_MESSAGE = 'sendMessage';
     public const ENDPOINT_DELETE_MESSAGE = 'deleteMessage';
     public const ENDPOINT_EDIT_MESSAGE = 'editMessageText';
+    public const ENDPOINT_EDIT_CAPTION = 'editMessageCaption';
     public const ENDPOINT_SEND_LOCATION = 'sendLocation';
     public const ENDPOINT_SEND_VOICE = 'sendVoice';
     public const ENDPOINT_SEND_CHAT_ACTION = 'sendChatAction';


Test patch:
diff --git a/src/Support/Testing/Fakes/TelegraphFake.php b/src/Support/Testing/Fakes/TelegraphFake.php
index ee1ec856..8d753eee 100644
--- a/src/Support/Testing/Fakes/TelegraphFake.php
+++ b/src/Support/Testing/Fakes/TelegraphFake.php
@@ -62,7 +62,7 @@ class TelegraphFake extends Telegraph
         return [
             'url' => $this->getApiUrl(),
             'endpoint' => $this->endpoint ?? null,
-            'data' => $this->data ?? [],
+            'data' => $this->prepareData(),
             'files' => $this->files,
             'bot_token' => $this->getBotIfAvailable()->token ?? null,
             'chat_id' => $this->getChatIfAvailable()->id ?? null,
diff --git a/tests/Sandbox/TelegraphTest.php b/tests/Sandbox/TelegraphTest.php
index a5aa628d..3ee606af 100644
--- a/tests/Sandbox/TelegraphTest.php
+++ b/tests/Sandbox/TelegraphTest.php
@@ -9,16 +9,14 @@ use DefStudio\Telegraph\Facades\Telegraph;
 use Illuminate\Support\Facades\Storage;
 use function Spatie\Snapshots\assertMatchesSnapshot;
 
-it('can return bot info', function () {
-    $bot = sandbox_bot();
+beforeEach(fn () => sandbox_bot());
 
-    $response = Telegraph::bot($bot)->botInfo()->send();
+it('can return bot info', function () {
+    $response = Telegraph::botInfo()->send();
     assertMatchesSnapshot($response->json('result'));
 })->skip(fn () => empty(env('SANDOBOX_TELEGRAM_BOT_TOKEN')) || env('SANDOBOX_TELEGRAM_BOT_TOKEN') === ':fake_bot_token:', 'Sandbox telegram bot token missing');
 
 it('can store chat files', function () {
-    $bot = sandbox_bot();
-
     Storage::fake();
 
     $photo = Photo::fromArray([
@@ -28,8 +26,7 @@ it('can store chat files', function () {
         "height" => 84,
     ]);
 
-    Telegraph::bot($bot)
-        ->store($photo, Storage::path('images/bot'), 'my_file.jpg');
+    Telegraph::store($photo, Storage::path('images/bot'), 'my_file.jpg');
 
     expect(Storage::exists('images/bot/my_file.jpg'))->toBeTrue();
 })->skip(fn () => empty(env('SANDOBOX_TELEGRAM_BOT_TOKEN')) || env('SANDOBOX_TELEGRAM_BOT_TOKEN') === ':fake_bot_token:', 'Sandbox telegram bot token missing');
diff --git a/tests/Unit/Concerns/SendsAttachmentsTest.php b/tests/Unit/Concerns/SendsAttachmentsTest.php
index 5920a24f..3b6741a8 100644
--- a/tests/Unit/Concerns/SendsAttachmentsTest.php
+++ b/tests/Unit/Concerns/SendsAttachmentsTest.php
@@ -244,13 +244,11 @@ it('can attach a voice while writing a message', function () {
         ->toMatchTelegramSnapshot();
 });
 
-
 it('can attach a voice with markdown caption', function () {
     expect(fn (Telegraph $telegraph) => $telegraph->voice(Storage::path('voice.ogg'))->markdown('listen **this** one'))
         ->toMatchTelegramSnapshot();
 });
 
-
 it('can attach a voice with html caption', function () {
     expect(fn (Telegraph $telegraph) => $telegraph->voice(Storage::path('voice.ogg'))->html('listen <b>this</b> one!'))
         ->toMatchTelegramSnapshot();
@@ -266,8 +264,6 @@ it('can send a voice protecting it from sharing', function () {
         ->toMatchTelegramSnapshot();
 });
 
-
-
 it('can send a voice replying to a message', function () {
     expect(fn (Telegraph $telegraph) => $telegraph->voice(Storage::path('voice.ogg'))->reply(1234))
         ->toMatchTelegramSnapshot();
@@ -300,3 +296,8 @@ test('voices are validated', function (string $path, bool $valid, string $except
         'message' => 'not found',
     ],
 ]);
+
+it('can edit a message caption', function () {
+    expect(fn (Telegraph $telegraph) => $telegraph->editCaption(42)->message('foo'))
+        ->toMatchTelegramSnapshot();
+});
diff --git a/tests/Unit/Models/TelegraphChatTest.php b/tests/Unit/Models/TelegraphChatTest.php
index cd20b912..1e97cf03 100644
--- a/tests/Unit/Models/TelegraphChatTest.php
+++ b/tests/Unit/Models/TelegraphChatTest.php
@@ -107,3 +107,15 @@ it('can send a voice', function () {
         'voice' => new Attachment(Storage::path('voice.ogg'), 'test'),
     ]);
 });
+
+it('can edit a message caption', function () {
+    Telegraph::fake();
+    $chat = make_chat();
+
+    $chat->editCaption(42)->markdown('test')->send();
+
+    Telegraph::assertSentData(\DefStudio\Telegraph\Telegraph::ENDPOINT_EDIT_CAPTION, [
+        'message_id' => 42,
+        'caption' => 'test',
+    ], false);
+});
diff --git a/tests/__snapshots__/SendsAttachmentsTest__it_can_edit_a_message_caption__1.yml b/tests/__snapshots__/SendsAttachmentsTest__it_can_edit_a_message_caption__1.yml
new file mode 100644
index 00000000..1b94eb1a
--- /dev/null
+++ b/tests/__snapshots__/SendsAttachmentsTest__it_can_edit_a_message_caption__1.yml
@@ -0,0 +1,7 @@
+url: 'https://api.telegram.org/bot3f3814e1-5836-3d77-904e-60f64b15df36/editMessageCaption'
+payload:
+    message_id: 42
+    chat_id: '-123456789'
+    parse_mode: html
+    caption: foo
+files: {  }