Passer au contenu principal

Vue d’ensemble

Le Feed Generator est le mécanisme des « flux algorithmiques » qui fonctionnent sur Bluesky. Vous pouvez publier vos propres flux basés sur des mots-clés ou des conditions utilisateur. Avec laravel-bluesky, vous pouvez facilement implémenter un Feed Generator dans une application Laravel.
Tutoriel officiel : Créer un flux personnalisé
Starter kit officiel : bluesky-social/feed-generator

Enregistrer un algorithme FeedGenerator

L’utilisation la plus simple consiste à enregistrer l’algorithme sous forme de closure dans AppServiceProvider::boot().
// À enregistrer dans AppServiceProvider::boot()

use Illuminate\Http\Request;
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\FeedGenerator\FeedGenerator;

FeedGenerator::register(name: 'artisan', algo: function(int $limit, ?string $cursor, ?string $user, Request $request): array {
    // Vous êtes libre de choisir l'implémentation.

    // Authentification requise en raison d'une restriction temporaire de l'API
    $response = Bluesky::login(identifier: config('bluesky.identifier'), password: config('bluesky.password'))
                       ->searchPosts(q: '#laravel', until: $cursor, limit: $limit);

    $cursor = data_get($response->collect('posts')->last(), 'indexedAt');

    $feed = $response->collect('posts')->map(function(array $post) {
        return ['post' => data_get($post, 'uri')];
    })->toArray();

    // Vous pouvez également utiliser l'objet Request pour renvoyer des résultats selon l'état de l'utilisateur.
    info('user: '.$user); // DID de l'utilisateur à l'origine de la requête. 'did:plc:***'
    info('header', $request->header());

    return compact('cursor', 'feed');
});
Utilisez pour name une chaîne compatible URL. La valeur de retour de l’algorithme est un tableau contenant cursor et feed.
[
    'cursor' => '',
    'feed' => [
       ['post' => 'at://'],
       ['post' => 'at://'],
    ],
]
Toutes les routes nécessaires au package sont enregistrées automatiquement.
  • http://localhost/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:web:example.com/app.bsky.feed.generator/artisan
  • http://localhost/xrpc/app.bsky.feed.describeFeedGenerator
  • http://localhost/.well-known/did.json
  • Le Service DID est généré automatiquement à partir de l’URL courante (par ex. did:web:example.com).
Vous n’avez qu’à décider vous-même du name du FeedGenerator et du contenu de l’implémentation.

Publier le flux (création de la commande)

Implémenter le FeedGenerator dans l’application Laravel ne le publie pas sur Bluesky. Créez et exécutez une commande qui appelle publishFeedGenerator.
1

Générer la commande

php artisan make:command PublishGeneratorCommand
2

Implémenter la commande

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Record\Generator;

class PublishGeneratorCommand extends Command
{
    protected $signature = 'bluesky:publish-generator';

    protected $description = 'Publier un FeedGenerator sur Bluesky';

    public function handle()
    {
        $generator = Generator::create(did: 'did:web:example.com', displayName: 'Feed name')
                              ->description('Feed description');

        $res = Bluesky::login(identifier: config('bluesky.identifier'), password: config('bluesky.password'))
                      ->publishFeedGenerator(name: 'artisan', generator: $generator);

        dump($res->json());

        return 0;
    }
}
3

Exécuter la commande

php artisan bluesky:publish-generator
En cas de succès, un lien est ajouté à la liste des flux dans le profil Bluesky. publishFeedGenerator se contente de mettre à jour les informations et peut être exécuté autant de fois que vous le souhaitez.

Créer plusieurs FeedGenerators

Il suffit d’appeler register plusieurs fois avec des name différents pour créer plusieurs flux.
// AppServiceProvider::boot()

use Revolution\Bluesky\FeedGenerator\FeedGenerator;

FeedGenerator::register(name: 'feed1', algo: function() {
    // Implémentation de feed1
});

FeedGenerator::register(name: 'feed2', algo: function() {
    // Implémentation de feed2
});
Dans la commande de publication, appelez également publishFeedGenerator plusieurs fois.
// PublishGeneratorCommand

Bluesky::login(identifier: config('bluesky.identifier'), password: config('bluesky.password'));

$generator1 = Generator::create(did: 'did:web:example.com', displayName: 'Feed 1')
                       ->description('Feed 1');
Bluesky::publishFeedGenerator(name: 'feed1', generator: $generator1);

$generator2 = Generator::create(did: 'did:web:example.com', displayName: 'Feed 2')
                       ->description('Feed 2');
Bluesky::publishFeedGenerator(name: 'feed2', generator: $generator2);

Séparer l’algorithme dans une classe

À la place d’une closure, l’utilisation d’une classe indépendante rend le code plus facile à organiser. Créez une classe callable implémentant le contrat FeedGeneratorAlgorithm et enregistrez-la dans AppServiceProvider.
// À créer à l'emplacement de votre choix

namespace App\FeedGenerator;

use Illuminate\Http\Request;
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Contracts\FeedGeneratorAlgorithm;

class ArtisanFeed implements FeedGeneratorAlgorithm
{
    public function __invoke(int $limit, ?string $cursor, ?string $user, Request $request): array
    {
        // Authentification requise en raison d'une restriction temporaire de l'API
        $response = Bluesky::login(identifier: config('bluesky.identifier'), password: config('bluesky.password'))
            ->searchPosts(q: '#laravel', until: $cursor, limit: $limit);

        $cursor = data_get($response->collect('posts')->last(), 'indexedAt');

        $feed = $response->collect('posts')->map(function (array $post) {
            return ['post' => data_get($post, 'uri')];
        })->toArray();

        info('user: '.$user);
        info('header', $request->header());

        return compact('cursor', 'feed');
    }
}
// AppServiceProvider::boot()

use Revolution\Bluesky\FeedGenerator\FeedGenerator;
use App\FeedGenerator\ArtisanFeed;

FeedGenerator::register(name: 'artisan', algo: ArtisanFeed::class);

Authentification

L’authentification, présente dans le starter kit officiel, est activée par défaut. Pour la désactiver, passez à validateAuthUsing une closure qui renvoie simplement le DID de l’utilisateur.
// AppServiceProvider::boot()

use Illuminate\Http\Request;
use Revolution\Bluesky\Crypto\JsonWebToken;
use Revolution\Bluesky\FeedGenerator\FeedGenerator;

FeedGenerator::validateAuthUsing(function (?string $jwt, Request $request): ?string {
    [, $payload] = JsonWebToken::explode($jwt);
    return data_get($payload, 'iss');
});
Les flux sont influencés par les « paramètres de langue » du compte. Si votre FeedGenerator récupère bien des posts mais que le flux n’apparaît pas sur Bluesky, vérifiez les paramètres de langue du compte.

Usages avancés

Vous pouvez enregistrer les posts en base via une commande Artisan et une planification, l’algorithme se contentant alors de les lire depuis la base. Vous obtenez ainsi un flux rapide sans appels d’API.
// Exemple : renvoyer le flux depuis la base de données

FeedGenerator::register(name: 'cached-feed', algo: function(int $limit, ?string $cursor): array {
    $query = \App\Models\Post::query()
        ->orderByDesc('indexed_at')
        ->limit($limit);

    if ($cursor) {
        $query->where('indexed_at', '<', $cursor);
    }

    $posts = $query->get();

    $cursor = $posts->last()?->indexed_at;

    $feed = $posts->map(fn ($post) => ['post' => $post->uri])->toArray();

    return compact('cursor', 'feed');
});
// Exemple : collecter régulièrement les posts via le planificateur (routes/console.php)

use Illuminate\Support\Facades\Schedule;

Schedule::command('bluesky:collect-posts')->everyFiveMinutes();
Dernière modification le 13 juillet 2026