Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Bluesky #1190

Merged
merged 3 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ TWITTER_CONSUMER_SECRET=
TWITTER_ACCESS_TOKEN=
TWITTER_ACCESS_SECRET=

BLUESKY_USERNAME=
BLUESKY_PASSWORD=

TELEGRAM_BOT_TOKEN=
TELEGRAM_CHANNEL=

Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ New threads will be automatically added to the index and threads which get updat
php artisan scout:flush App\\Models\\Thread
```

### X (Twitter) Sharing (optional)
### Social Media Sharing (optional)

To enable published articles to be automatically shared on X, you'll need to [create an app](https://developer.x.com/apps/). Once the app has been created, update the below variables in your `.env` file. The consumer key and secret and access token and secret can be found in the `Keys and tokens` section of the X developers UI.

Expand All @@ -103,6 +103,13 @@ TWITTER_ACCESS_TOKEN=
TWITTER_ACCESS_SECRET=
```

To do the same for Bluesky you simply need to set up the app keys with your login and password:

```
BLUESKY_USERNAME=
BLUESKY_PASSWORD=
```

Approved articles are shared in the order they were submitted for approval. Articles are shared twice per day at 14:00 and 18:00 UTC. Once an article has been shared, it will not be shared again.

### Telegram Notifications (optional)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@
namespace App\Console\Commands;

use App\Models\Article;
use App\Notifications\PostArticleToTwitter as PostArticleToTwitterNotification;
use App\Notifications\PostArticleToBluesky;
use App\Notifications\PostArticleToTwitter;
use Illuminate\Console\Command;
use Illuminate\Notifications\AnonymousNotifiable;

final class PostArticleToTwitter extends Command
final class PostArticleToSocialMedia extends Command
{
protected $signature = 'lio:post-article-to-twitter';
protected $signature = 'lio:post-article-to-social-media';

protected $description = 'Posts the latest unshared article to X';
protected $description = 'Posts the latest unshared article to social media';

public function handle(AnonymousNotifiable $notifiable): void
{
if ($article = Article::nextForSharing()) {
$notifiable->notify(new PostArticleToTwitterNotification($article));
$notifiable->notify(new PostArticleToBluesky($article));
$notifiable->notify(new PostArticleToTwitter($article));

$article->markAsShared();
}
Expand Down
6 changes: 6 additions & 0 deletions app/Http/Requests/UpdateProfileRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public function rules(): array
'email' => 'required|email|max:255|unique:users,email,'.Auth::id(),
'username' => 'required|alpha_dash|max:255|unique:users,username,'.Auth::id(),
'twitter' => 'max:255|nullable|unique:users,twitter,'.Auth::id(),
'bluesky' => 'max:255|nullable|unique:users,bluesky,'.Auth::id(),
'website' => 'max:255|nullable|url',
'bio' => 'max:160',
];
Expand Down Expand Up @@ -43,6 +44,11 @@ public function twitter(): ?string
return $this->get('twitter');
}

public function bluesky(): ?string
{
return $this->get('bluesky');
}

public function website(): ?string
{
return $this->get('website');
Expand Down
1 change: 1 addition & 0 deletions app/Http/Resources/AuthorResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public function toArray($request): array
'name' => $this->name(),
'bio' => $this->bio(),
'twitter_handle' => $this->twitter(),
'bluesky_handle' => $this->bluesky(),
'github_username' => $this->githubUsername(),
];
}
Expand Down
3 changes: 2 additions & 1 deletion app/Jobs/UpdateProfile.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public function __construct(
array $attributes = []
) {
$this->attributes = Arr::only($attributes, [
'name', 'email', 'username', 'github_username', 'bio', 'twitter', 'website',
'name', 'email', 'username', 'github_username', 'bio', 'twitter', 'bluesky', 'website',
]);
}

Expand All @@ -28,6 +28,7 @@ public static function fromRequest(User $user, UpdateProfileRequest $request): s
'username' => strtolower($request->username()),
'bio' => trim(strip_tags($request->bio())),
'twitter' => $request->twitter(),
'bluesky' => $request->bluesky(),
'website' => $request->website(),
]);
}
Expand Down
11 changes: 11 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ final class User extends Authenticatable implements MustVerifyEmail
'name',
'email',
'twitter',
'bluesky',
'website',
'username',
'password',
Expand Down Expand Up @@ -110,6 +111,11 @@ public function twitter(): ?string
return $this->twitter;
}

public function bluesky(): ?string
{
return $this->bluesky;
}

public function website(): ?string
{
return $this->website;
Expand All @@ -120,6 +126,11 @@ public function hasTwitterAccount(): bool
return ! empty($this->twitter());
}

public function hasBlueskyAccount(): bool
{
return ! empty($this->bluesky());
}

public function hasWebsite(): bool
{
return ! empty($this->website());
Expand Down
42 changes: 42 additions & 0 deletions app/Notifications/PostArticleToBluesky.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace App\Notifications;

use App\Models\Article;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use NotificationChannels\Bluesky\BlueskyChannel;
use NotificationChannels\Bluesky\BlueskyPost;

class PostArticleToBluesky extends Notification
{
use Queueable;

public function __construct(private Article $article) {}

public function via($notifiable): array
{
return [BlueskyChannel::class];
}

public function toBluesky($notifiable)
{
return BlueskyPost::make()
->text($this->generatePost());
}

public function generatePost(): string
{
$title = $this->article->title();
$url = route('articles.show', $this->article->slug());
$author = $this->article->author();
$author = $author->bluesky() ? "@{$author->bluesky()}" : $author->name();

return "{$title} by {$author}\n\n{$url}";
}

public function article()
{
return $this->article;
}
}
2 changes: 1 addition & 1 deletion app/Notifications/PostArticleToTwitter.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public function toTwitter($notifiable)
return new TwitterStatusUpdate($this->generateTweet());
}

public function generateTweet()
public function generateTweet(): string
{
$title = $this->article->title();
$url = route('articles.show', $this->article->slug());
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"blade-ui-kit/blade-zondicons": "^1.5",
"codeat3/blade-simple-icons": "^5.0",
"guzzlehttp/guzzle": "^7.2",
"innocenzi/bluesky-notification-channel": "^0.2.0",
"intervention/image": "^2.7",
"laravel-notification-channels/telegram": "^5.0",
"laravel-notification-channels/twitter": "^8.1.1",
Expand Down
75 changes: 74 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
'access_secret' => env('TWITTER_ACCESS_SECRET'),
],

'bluesky' => [
'username' => env('BLUESKY_USERNAME'),
'password' => env('BLUESKY_PASSWORD'),
],

'telegram-bot-api' => [
'token' => env('TELEGRAM_BOT_TOKEN'),
'channel' => env('TELEGRAM_CHANNEL'),
Expand Down
1 change: 1 addition & 0 deletions database/factories/UserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public function definition(): array
'github_id' => $this->faker->unique()->numberBetween(10000, 99999),
'github_username' => $this->faker->unique()->userName(),
'twitter' => $this->faker->unique()->userName(),
'bluesky' => $this->faker->unique()->userName(),
'website' => 'https://laravel.io',
'banned_at' => null,
'banned_reason' => null,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('bluesky')->nullable()->after('twitter');
});
}
};
4 changes: 4 additions & 0 deletions resources/svg/bluesky.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions resources/views/articles/show.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ class="prose prose-lg text-gray-800 prose-lio"
</a>
@endif

@if ($article->author()->hasBlueskyAccount())
<a href="https://bsky.app/profile/{{ $article->author()->bluesky() }}" class="text-twitter">
<x-icon-bluesky class="w-6 h-6" />
</a>
@endif

@if ($article->author()->hasWebsite())
<a href="{{ $article->author()->website() }}">
<x-heroicon-o-globe-alt class="w-6 h-6" />
Expand Down
4 changes: 2 additions & 2 deletions resources/views/components/articles/form.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,10 @@ class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:t
</div>
</div>

@unless (Auth::user()->twitter())
@unless (Auth::user()->hasTwitterAccount() && Auth::user()->hasBlueskyAccount())
<span class="text-gray-600 text-sm mt-4 block">
Articles will be shared on X (Twitter).
<a href="{{ route('settings.profile') }}" class="text-green-darker">Add your X (Twitter) handle</a> and we'll include that too.
<a href="{{ route('settings.profile') }}" class="text-green-darker">Add your X (Twitter) and/or Bluesky handles</a> and we'll include that too.
</span>
@endunless
</div>
Expand Down
6 changes: 6 additions & 0 deletions resources/views/components/users/profile-block.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
</a>
@endif

@if ($user->hasBlueskyAccount())
<a href="https://bsky.app/{{ $user->bluesky() }}" class="text-twitter">
<x-icon-bluesky class="w-6 h-6" />
</a>
@endif

@if ($user->hasWebsite())
<a href="{{ $user->website() }}">
<x-heroicon-o-globe-alt class="w-6 h-6" />
Expand Down
5 changes: 5 additions & 0 deletions resources/views/layouts/_footer.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@
(Twitter)
</a>

<a href="https://bsky.app/profile/laravel.io" class="w-1/2 text-gray-400 mb-4 hover:text-gray-200 lg:mb-6 whitespace-nowrap">
<x-icon-bluesky class="text-white w-4 h-4 inline mr-2"/>
Bluesky
</a>

<a href="https://github.com/laravelio" class="w-1/2 text-gray-400 mb-4 hover:text-gray-200 lg:mb-6 whitespace-nowrap">
<x-icon-github class="text-white w-4 h-4 inline mr-2"/>
GitHub
Expand Down
Loading