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

> Hoe je met Laravel Bluesky een eigen algoritmische feed maakt en publiceert. Uitleg over registratie, commands maken, meerdere feeds, klassen scheiden, authenticatie en geavanceerd gebruik.

## Overzicht

Een Feed Generator is het mechanisme achter "algoritmische feeds" op Bluesky. Je kunt eigen feeds publiceren op basis van bijvoorbeeld specifieke keywords of gebruikerscriteria. Met `laravel-bluesky` implementeer je eenvoudig een Feed Generator in je Laravel-applicatie.

<Info>
  Officiële tutorial: [Custom feeds maken](https://atproto.com/ja/guides/custom-feed-tutorial)
</Info>

<Info>
  Officiële starter kit: [bluesky-social/feed-generator](https://github.com/bluesky-social/feed-generator)
</Info>

```mermaid theme={null}
sequenceDiagram
    participant Bluesky as Bluesky<br>server
    participant App as Laravel<br>app
    participant DB as Database

    Bluesky->>App: GET /xrpc/app.bsky.feed.getFeedSkeleton
    App->>DB: Feeddata ophalen
    DB-->>App: posts
    App-->>Bluesky: { cursor, feed }
```

## Een FeedGenerator-algoritme registreren

De eenvoudigste aanpak is om het algoritme als closure te registreren in `AppServiceProvider::boot()`.

```php theme={null}
// Registreren in 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 {
    // De implementatie mag je zelf bepalen.

    // Authenticatie is vereist vanwege een tijdelijke API-beperking
    $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();

    // Je kunt via het Request-object ook resultaten teruggeven afgestemd op de gebruiker.
    info('user: '.$user); // De DID van de gebruiker die het request doet. 'did:plc:***'
    info('header', $request->header());

    return compact('cursor', 'feed');
});
```

Gebruik voor `name` een URL-veilige string.

De returnwaarde van het algoritme is een array met `cursor` en `feed`.

```php theme={null}
[
    'cursor' => '',
    'feed' => [
       ['post' => 'at://'],
       ['post' => 'at://'],
    ],
]
```

Alle routes die het pakket nodig heeft, worden automatisch geregistreerd.

* `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`
* De Service DID wordt automatisch gegenereerd op basis van de huidige URL (bijvoorbeeld `did:web:example.com`).

<Tip>
  Het enige dat je zelf bepaalt, is de `name` van de FeedGenerator en de implementatie.
</Tip>

## De feed publiceren (een command maken)

Alleen een FeedGenerator implementeren in je Laravel-app is niet genoeg om deze op Bluesky te publiceren. Maak een command dat `publishFeedGenerator` aanroept en voer het uit.

<Steps>
  <Step title="Het command genereren">
    ```bash theme={null}
    php artisan make:command PublishGeneratorCommand
    ```
  </Step>

  <Step title="Het command implementeren">
    ```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 = 'De FeedGenerator publiceren op 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="Het command uitvoeren">
    ```bash theme={null}
    php artisan bluesky:publish-generator
    ```

    Bij succes wordt er een link toegevoegd aan de feedlijst op je Bluesky-profiel. `publishFeedGenerator` werkt alleen informatie bij, dus je kunt het zo vaak uitvoeren als je wilt.
  </Step>
</Steps>

## Meerdere FeedGenerators maken

Je maakt meerdere feeds door simpelweg `register` meerdere keren aan te roepen met een andere `name`.

```php theme={null}
// AppServiceProvider::boot()

use Revolution\Bluesky\FeedGenerator\FeedGenerator;

FeedGenerator::register(name: 'feed1', algo: function() {
    // Implementatie van feed1
});

FeedGenerator::register(name: 'feed2', algo: function() {
    // Implementatie van feed2
});
```

In het publicatiecommand roep je op dezelfde manier `publishFeedGenerator` meerdere keren aan.

```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);
```

## Het algoritme in een aparte klasse

Door in plaats van een closure een aparte klasse te gebruiken, houd je je code beter georganiseerd. Maak een callable klasse die het `FeedGeneratorAlgorithm`-contract implementeert en registreer deze in de `AppServiceProvider`.

```php theme={null}
// Maak deze aan op een willekeurige plek

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
    {
        // Authenticatie is vereist vanwege een tijdelijke API-beperking
        $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);
```

## Authenticatie

De authenticatiefunctionaliteit van de officiële starter kit is standaard ingeschakeld. Om deze uit te schakelen geef je aan `validateAuthUsing` een closure door die simpelweg de gebruikers-DID teruggeeft.

```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>
  Feeds worden beïnvloed door de "taalinstellingen" van het account. Als de FeedGenerator wel posts ophaalt maar de feed niet zichtbaar is op Bluesky, controleer dan de taalinstellingen van je account.
</Warning>

## Geavanceerd gebruik

Door met een Artisan-command en de task scheduler posts in een database op te slaan en in het algoritme alleen uit de database te lezen, krijg je een snelle feed zonder API-aanroepen.

```php theme={null}
// Voorbeeld: het algoritme geeft de feed terug vanuit de database

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}
// Voorbeeld: periodiek posts verzamelen via de scheduler (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

- [Testen](/nl/packages/laravel-bluesky/testing.md)
- [BlueskyManager en HasShortHand](/nl/packages/laravel-bluesky/bluesky-manager.md)
- [Bottutorial - Laravel Bluesky](/nl/packages/laravel-bluesky/bot-tutorial.md)
- [Laravel Bluesky](/nl/packages/laravel-bluesky/index.md)
- [Crypto — AT Protocol-cryptografie](/nl/packages/laravel-bluesky/crypto.md)
