> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Feed Generator

> Comment créer et publier vos propres flux algorithmiques avec Laravel Bluesky. Enregistrement, création de commandes, plusieurs flux, séparation en classes, authentification et usages avancés.

## 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.

<Info>
  Tutoriel officiel : [Créer un flux personnalisé](https://atproto.com/ja/guides/custom-feed-tutorial)
</Info>

<Info>
  Starter kit officiel : [bluesky-social/feed-generator](https://github.com/bluesky-social/feed-generator)
</Info>

```mermaid theme={null}
sequenceDiagram
    participant Bluesky as Serveur<br>Bluesky
    participant App as Application<br>Laravel
    participant DB as Base de données

    Bluesky->>App: GET /xrpc/app.bsky.feed.getFeedSkeleton
    App->>DB: Récupération des données du flux
    DB-->>App: posts
    App-->>Bluesky: { cursor, feed }
```

## Enregistrer un algorithme FeedGenerator

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

```php theme={null}
// À 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`.

```php theme={null}
[
    '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`).

<Tip>
  Vous n'avez qu'à décider vous-même du `name` du FeedGenerator et du contenu de l'implémentation.
</Tip>

## 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`.

<Steps>
  <Step title="Générer la commande">
    ```bash theme={null}
    php artisan make:command PublishGeneratorCommand
    ```
  </Step>

  <Step title="Implémenter la commande">
    ```php theme={null}
    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;
        }
    }
    ```
  </Step>

  <Step title="Exécuter la commande">
    ```bash theme={null}
    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.
  </Step>
</Steps>

## Créer plusieurs FeedGenerators

Il suffit d'appeler `register` plusieurs fois avec des `name` différents pour créer plusieurs flux.

```php theme={null}
// 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.

```php theme={null}
// 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`.

```php theme={null}
// À 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');
    }
}
```

```php theme={null}
// 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.

```php theme={null}
// 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');
});
```

<Warning>
  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.
</Warning>

## 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.

```php theme={null}
// 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');
});
```

```php theme={null}
// Exemple : collecter régulièrement les posts via le planificateur (routes/console.php)

use Illuminate\Support\Facades\Schedule;

Schedule::command('bluesky:collect-posts')->everyFiveMinutes();
```

<Info>
  Source : [docs/feed-generator.md](https://github.com/invokable/laravel-bluesky/blob/main/docs/feed-generator.md)
</Info>


## Related topics

- [Tests](/fr/packages/laravel-bluesky/testing.md)
- [BlueskyManager et HasShortHand](/fr/packages/laravel-bluesky/bluesky-manager.md)
- [Tutoriel bot - Laravel Bluesky](/fr/packages/laravel-bluesky/bot-tutorial.md)
- [Crypto — chiffrement AT Protocol](/fr/packages/laravel-bluesky/crypto.md)
- [Routes](/fr/packages/laravel-bluesky/route.md)
