diff --git a/app/Services/Social/Discord/DiscordPublisher.php b/app/Services/Social/Discord/DiscordPublisher.php index d624473b..d741d01f 100644 --- a/app/Services/Social/Discord/DiscordPublisher.php +++ b/app/Services/Social/Discord/DiscordPublisher.php @@ -126,7 +126,14 @@ private function sendWithMedia(string $channelId, array $payload, Collection $me $filename = $item->original_filename ?: (basename($item->path) ?: "media-{$index}"); $request = $request->attach("files[{$index}]", file_get_contents($tempFile), $filename); - $attachments[] = ['id' => $index, 'filename' => $filename]; + + $attachment = ['id' => $index, 'filename' => $filename]; + + if ($alt = $item->altText()) { + $attachment['description'] = mb_substr($alt, 0, Platform::Discord->altTextMaxLength()); + } + + $attachments[] = $attachment; } $payload['attachments'] = $attachments; diff --git a/tests/Feature/Services/Social/DiscordPublisherTest.php b/tests/Feature/Services/Social/DiscordPublisherTest.php index aa7687d0..aab8c694 100644 --- a/tests/Feature/Services/Social/DiscordPublisherTest.php +++ b/tests/Feature/Services/Social/DiscordPublisherTest.php @@ -168,6 +168,84 @@ function fakeDiscord(array $messageResponse = ['id' => '777'], int $status = 200 }); }); +test('sets the attachment description from image alt text, capped at the platform max', function () { + $longAlt = str_repeat('a', 2000); + + $this->post->update([ + 'media' => [[ + 'id' => 'm1', + 'path' => 'media/2026-01/pic.jpg', + 'url' => 'https://example.com/media/2026-01/pic.jpg', + 'mime_type' => 'image/jpeg', + 'original_filename' => 'pic.jpg', + 'meta' => ['alt_text' => $longAlt], + ]], + ]); + + $this->mock(MediaOptimizer::class) + ->shouldReceive('optimizeImage') + ->andReturnUsing(fn () => tap(tempnam(sys_get_temp_dir(), 'discord_test_'), fn ($f) => file_put_contents($f, str_repeat('x', 1024)))); + + Http::fake([ + config('trypost.platforms.discord.api').'/guilds/*/channels' => Http::response([['id' => '444555666', 'name' => 'general', 'type' => 0]], 200), + config('trypost.platforms.discord.api').'/guilds/*/roles' => Http::response([['id' => '111222333', 'name' => '@everyone', 'permissions' => '3072']], 200), + config('trypost.platforms.discord.api').'/guilds/*/members/*' => Http::response(['roles' => []], 200), + 'example.com/*' => Http::response(str_repeat('x', 1024), 200), + config('trypost.platforms.discord.api').'/channels/*/messages' => Http::response(['id' => '902'], 200), + ]); + + $this->publisher->publish(($this->makePostPlatform)()); + + $expectedAlt = mb_substr($longAlt, 0, Platform::Discord->altTextMaxLength()); + + Http::assertSent(function ($request) use ($expectedAlt) { + if (! str_contains($request->url(), '/messages')) { + return false; + } + $payloadPart = collect($request->data())->firstWhere('name', 'payload_json'); + $payload = json_decode(data_get($payloadPart, 'contents'), true); + + return data_get($payload, 'attachments.0.description') === $expectedAlt + && mb_strlen($expectedAlt) === Platform::Discord->altTextMaxLength(); + }); +}); + +test('omits the attachment description when the image has no alt text', function () { + $this->post->update([ + 'media' => [[ + 'id' => 'm1', + 'path' => 'media/2026-01/pic.jpg', + 'url' => 'https://example.com/media/2026-01/pic.jpg', + 'mime_type' => 'image/jpeg', + 'original_filename' => 'pic.jpg', + ]], + ]); + + $this->mock(MediaOptimizer::class) + ->shouldReceive('optimizeImage') + ->andReturnUsing(fn () => tap(tempnam(sys_get_temp_dir(), 'discord_test_'), fn ($f) => file_put_contents($f, str_repeat('x', 1024)))); + + Http::fake([ + config('trypost.platforms.discord.api').'/guilds/*/channels' => Http::response([['id' => '444555666', 'name' => 'general', 'type' => 0]], 200), + config('trypost.platforms.discord.api').'/guilds/*/roles' => Http::response([['id' => '111222333', 'name' => '@everyone', 'permissions' => '3072']], 200), + config('trypost.platforms.discord.api').'/guilds/*/members/*' => Http::response(['roles' => []], 200), + 'example.com/*' => Http::response(str_repeat('x', 1024), 200), + config('trypost.platforms.discord.api').'/channels/*/messages' => Http::response(['id' => '903'], 200), + ]); + + $this->publisher->publish(($this->makePostPlatform)()); + + Http::assertSent(function ($request) { + if (! str_contains($request->url(), '/messages')) { + return false; + } + $payloadPart = collect($request->data())->firstWhere('name', 'payload_json'); + $payload = json_decode(data_get($payloadPart, 'contents'), true); + + return ! array_key_exists('description', data_get($payload, 'attachments.0')); + }); +}); + test('throws when no channel is selected', function () { expect(fn () => $this->publisher->publish(($this->makePostPlatform)(meta: []))) ->toThrow(DiscordPublishException::class);