From cbbe87e23ccfa22dca497410d2b9e42e0f1f09cf Mon Sep 17 00:00:00 2001 From: Jonny Barnes Date: Sat, 23 Mar 2024 21:19:54 +0000 Subject: [PATCH] Setup support for syndicating to Bluesky --- app/Jobs/SyndicateNoteToBluesky.php | 63 +++++++++++++++++ app/Services/NoteService.php | 11 ++- config/bridgy.php | 13 ++++ ..._204940_add_bluesky_syndication_column.php | 28 ++++++++ database/seeders/NotesTableSeeder.php | 1 + resources/views/templates/note.blade.php | 4 +- .../views/templates/social-links.blade.php | 5 ++ tests/Feature/MicropubControllerTest.php | 19 +++-- .../Jobs/SyndicateNoteToBlueskyJobTest.php | 69 +++++++++++++++++++ 9 files changed, 204 insertions(+), 9 deletions(-) create mode 100644 app/Jobs/SyndicateNoteToBluesky.php create mode 100644 database/migrations/2024_03_23_204940_add_bluesky_syndication_column.php create mode 100644 tests/Unit/Jobs/SyndicateNoteToBlueskyJobTest.php diff --git a/app/Jobs/SyndicateNoteToBluesky.php b/app/Jobs/SyndicateNoteToBluesky.php new file mode 100644 index 00000000..5f3bf801 --- /dev/null +++ b/app/Jobs/SyndicateNoteToBluesky.php @@ -0,0 +1,63 @@ +request( + 'POST', + 'https://brid.gy/micropub', + [ + 'headers' => [ + 'Authorization' => 'Bearer ' . config('bridgy.bluesky_token'), + ], + 'json' => [ + 'type' => ['h-entry'], + 'properties' => [ + 'content' => [$this->note->getRawOriginal('note')], + ], + ], + ] + ); + + // Parse for syndication URL + if ($response->getStatusCode() === 201) { + $this->note->bluesky_url = $response->getHeader('Location')[0];; + $this->note->save(); + } + } +} diff --git a/app/Services/NoteService.php b/app/Services/NoteService.php index 1238b804..b101498c 100644 --- a/app/Services/NoteService.php +++ b/app/Services/NoteService.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Services; use App\Jobs\SendWebMentions; +use App\Jobs\SyndicateNoteToBluesky; use App\Jobs\SyndicateNoteToMastodon; use App\Models\Media; use App\Models\Note; @@ -53,6 +54,10 @@ class NoteService extends Service dispatch(new SyndicateNoteToMastodon($note)); } + if (in_array('bluesky', $this->getSyndicationTargets($request), true)) { + dispatch(new SyndicateNoteToBluesky($note)); + } + return $note; } @@ -156,12 +161,12 @@ class NoteService extends Service $mpSyndicateTo = Arr::wrap($mpSyndicateTo); foreach ($mpSyndicateTo as $uid) { $target = SyndicationTarget::where('uid', $uid)->first(); - if ($target && $target->service_name === 'Twitter') { - $syndication[] = 'twitter'; - } if ($target && $target->service_name === 'Mastodon') { $syndication[] = 'mastodon'; } + if ($target && $target->service_name === 'Bluesky') { + $syndication[] = 'bluesky'; + } } return $syndication; diff --git a/config/bridgy.php b/config/bridgy.php index 9717625f..5314afa4 100644 --- a/config/bridgy.php +++ b/config/bridgy.php @@ -15,4 +15,17 @@ return [ 'mastodon_token' => env('BRIDGY_MASTODON_TOKEN'), + /* + |-------------------------------------------------------------------------- + | Bluesky Token + |-------------------------------------------------------------------------- + | + | When syndicating posts to Bluesky using Brid.gy’s Micropub endpoint, we + | need to provide an access token. This token can be generated by going to + | https://brid.gy/bluesky and clicking the “Get token” button. + | + */ + + 'bluesky_token' => env('BRIDGY_BLUESKY_TOKEN'), + ]; diff --git a/database/migrations/2024_03_23_204940_add_bluesky_syndication_column.php b/database/migrations/2024_03_23_204940_add_bluesky_syndication_column.php new file mode 100644 index 00000000..864d949c --- /dev/null +++ b/database/migrations/2024_03_23_204940_add_bluesky_syndication_column.php @@ -0,0 +1,28 @@ +string('bluesky_url')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('notes', function (Blueprint $table) { + $table->dropColumn('bluesky_url'); + }); + } +}; diff --git a/database/seeders/NotesTableSeeder.php b/database/seeders/NotesTableSeeder.php index 4a42f379..298205e3 100644 --- a/database/seeders/NotesTableSeeder.php +++ b/database/seeders/NotesTableSeeder.php @@ -138,6 +138,7 @@ class NotesTableSeeder extends Seeder $noteSyndicated->swarm_url = 'https://www.swarmapp.com/checking/123456789'; $noteSyndicated->instagram_url = 'https://www.instagram.com/p/aWsEd123Jh'; $noteSyndicated->mastodon_url = 'https://mastodon.social/@jonnybarnes/123456789'; + $noteSyndicated->bluesky_url = 'https://bsky.app/profile/jonnybarnes.uk/post/123456789'; $noteSyndicated->save(); DB::table('notes') ->where('id', $noteSyndicated->id) diff --git a/resources/views/templates/note.blade.php b/resources/views/templates/note.blade.php index 83b799d4..fb4bb58f 100644 --- a/resources/views/templates/note.blade.php +++ b/resources/views/templates/note.blade.php @@ -71,7 +71,8 @@ $note->facebook_url || $note->swarm_url || $note->instagram_url || - $note->mastodon_url + $note->mastodon_url || + $note->bluesky_url ) @include('templates.social-links', [ 'id' => $note->id, @@ -80,6 +81,7 @@ 'swarm_url' => $note->swarm_url, 'instagram_url' => $note->instagram_url, 'mastodon_url' => $note->mastodon_url, + 'bluesky_url' => $note->bluesky_url ]) @endif diff --git a/resources/views/templates/social-links.blade.php b/resources/views/templates/social-links.blade.php index 95aa1092..c884ac6b 100644 --- a/resources/views/templates/social-links.blade.php +++ b/resources/views/templates/social-links.blade.php @@ -39,3 +39,8 @@ @endif +@if($bluesky_url !== null) + + + +@endif diff --git a/tests/Feature/MicropubControllerTest.php b/tests/Feature/MicropubControllerTest.php index e3054a20..412401d1 100644 --- a/tests/Feature/MicropubControllerTest.php +++ b/tests/Feature/MicropubControllerTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Tests\Feature; use App\Jobs\SendWebMentions; +use App\Jobs\SyndicateNoteToBluesky; use App\Jobs\SyndicateNoteToMastodon; use App\Models\Media; use App\Models\Note; @@ -123,18 +124,18 @@ class MicropubControllerTest extends TestCase } /** @test */ - public function micropubClientCanRequestTheNewNoteIsSyndicatedToTwitterAndMastodon(): void + public function micropubClientCanRequestTheNewNoteIsSyndicatedToMastodonAndBluesky(): void { Queue::fake(); - SyndicationTarget::factory()->create([ - 'uid' => 'https://twitter.com/jonnybarnes', - 'service_name' => 'Twitter', - ]); SyndicationTarget::factory()->create([ 'uid' => 'https://mastodon.social/@jonnybarnes', 'service_name' => 'Mastodon', ]); + SyndicationTarget::factory()->create([ + 'uid' => 'https://bsky.app/profile/jonnybarnes.uk', + 'service_name' => 'Bluesky', + ]); $faker = Factory::create(); $note = $faker->text; @@ -145,6 +146,7 @@ class MicropubControllerTest extends TestCase 'content' => $note, 'mp-syndicate-to' => [ 'https://mastodon.social/@jonnybarnes', + 'https://bsky.app/profile/jonnybarnes.uk', ], ], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()] @@ -152,6 +154,7 @@ class MicropubControllerTest extends TestCase $response->assertJson(['response' => 'created']); $this->assertDatabaseHas('notes', ['note' => $note]); Queue::assertPushed(SyndicateNoteToMastodon::class); + Queue::assertPushed(SyndicateNoteToBluesky::class); } /** @test */ @@ -249,6 +252,10 @@ class MicropubControllerTest extends TestCase 'uid' => 'https://mastodon.social/@jonnybarnes', 'service_name' => 'Mastodon', ]); + SyndicationTarget::factory()->create([ + 'uid' => 'https://bsky.app/profile/jonnybarnes.uk', + 'service_name' => 'Bluesky', + ]); $faker = Factory::create(); $note = $faker->text; @@ -261,6 +268,7 @@ class MicropubControllerTest extends TestCase 'in-reply-to' => ['https://aaronpk.localhost'], 'mp-syndicate-to' => [ 'https://mastodon.social/@jonnybarnes', + 'https://bsky.app/profile/jonnybarnes.uk', ], 'photo' => [config('filesystems.disks.s3.url') . '/test-photo.jpg'], ], @@ -272,6 +280,7 @@ class MicropubControllerTest extends TestCase ->assertJson(['response' => 'created']); Queue::assertPushed(SendWebMentions::class); Queue::assertPushed(SyndicateNoteToMastodon::class); + Queue::assertPushed(SyndicateNoteToBluesky::class); } /** diff --git a/tests/Unit/Jobs/SyndicateNoteToBlueskyJobTest.php b/tests/Unit/Jobs/SyndicateNoteToBlueskyJobTest.php new file mode 100644 index 00000000..e1e3fb8c --- /dev/null +++ b/tests/Unit/Jobs/SyndicateNoteToBlueskyJobTest.php @@ -0,0 +1,69 @@ + 'test']); + $faker = Factory::create(); + $randomNumber = $faker->randomNumber(); + $mock = new MockHandler([ + new Response(201, ['Location' => 'https://bsky.app/profile/jonnybarnes.uk/' . $randomNumber]), + ]); + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + + $note = Note::factory()->create(); + $job = new SyndicateNoteToBluesky($note); + $job->handle($client); + + $this->assertDatabaseHas('notes', [ + 'bluesky_url' => 'https://bsky.app/profile/jonnybarnes.uk/' . $randomNumber, + ]); + } + + /** @test */ + public function weSyndicateTheOriginalMarkdownToBluesky(): void + { + config(['bridgy.bluesky_token' => 'test']); + $faker = Factory::create(); + $randomNumber = $faker->randomNumber(); + + $container = []; + $history = Middleware::history($container); + $mock = new MockHandler([ + new Response(201, ['Location' => 'https://bsky.app/profile/jonnybarnes.uk/' . $randomNumber]), + ]); + $handler = HandlerStack::create($mock); + $handler->push($history); + $client = new Client(['handler' => $handler]); + + $note = Note::factory()->create(['note' => 'This is a **test**']); + $job = new SyndicateNoteToBluesky($note); + $job->handle($client); + + $this->assertDatabaseHas('notes', [ + 'bluesky_url' => 'https://bsky.app/profile/jonnybarnes.uk/' . $randomNumber, + ]); + + $expectedRequestContent = '{"type":["h-entry"],"properties":{"content":["This is a **test**"]}}'; + + $this->assertEquals($expectedRequestContent, $container[0]['request']->getBody()->getContents()); + } +}