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

> Cómo crear y publicar feeds algorítmicos personalizados con Laravel Bluesky. Registro, creación de comandos, feeds múltiples, separación de clases, autenticación y uso avanzado.

## Descripción general

Feed Generator es el mecanismo de "feeds algorítmicos" que se ejecuta sobre Bluesky. Permite publicar feeds propios adaptados a palabras clave o condiciones de usuario. Con `laravel-bluesky` puedes implementar fácilmente un Feed Generator dentro de una aplicación Laravel.

<Info>
  Tutorial oficial: [Crear un feed personalizado](https://atproto.com/ja/guides/custom-feed-tutorial)
</Info>

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

```mermaid theme={null}
sequenceDiagram
    participant Bluesky as Servidor<br>de Bluesky
    participant App as Aplicación<br>Laravel
    participant DB as Base de datos

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

## Registro del algoritmo del FeedGenerator

La forma más sencilla es registrar el algoritmo como clausura en `AppServiceProvider::boot()`.

```php theme={null}
// Registrar en 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 {
    // Implementa como quieras.

    // Por una restricción temporal de la API, se requiere autenticación
    $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();

    // También puedes usar el objeto Request para devolver resultados según el estado del usuario.
    info('user: '.$user); // DID del usuario que hace la petición. 'did:plc:***'
    info('header', $request->header());

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

En `name` utiliza una cadena URL-safe.

El valor de retorno del algoritmo es un array que contiene `cursor` y `feed`.

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

El paquete registra automáticamente todas las rutas necesarias.

* `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`
* El Service DID se genera automáticamente a partir de la URL actual (por ejemplo, `did:web:example.com`).

<Tip>
  Lo único que tienes que decidir es el `name` del FeedGenerator y su implementación.
</Tip>

## Publicar el feed (crear un comando)

Con solo implementar el FeedGenerator dentro de la aplicación Laravel no se publica en Bluesky. Crea un comando que invoque `publishFeedGenerator` y ejecútalo.

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

  <Step title="Implementa el comando">
    ```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 = 'Publica el FeedGenerator en 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="Ejecuta el comando">
    ```bash theme={null}
    php artisan bluesky:publish-generator
    ```

    Si tiene éxito, se añade un enlace a la lista de feeds del perfil de Bluesky. Como `publishFeedGenerator` solo actualiza información, puedes ejecutarlo tantas veces como quieras.
  </Step>
</Steps>

## Crear varios FeedGenerator

Basta con cambiar el `name` y llamar a `register` varias veces para crear múltiples feeds.

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

use Revolution\Bluesky\FeedGenerator\FeedGenerator;

FeedGenerator::register(name: 'feed1', algo: function() {
    // implementación de feed1
});

FeedGenerator::register(name: 'feed2', algo: function() {
    // implementación de feed2
});
```

En el comando de publicación llamas también varias veces a `publishFeedGenerator`.

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

## Separar el algoritmo en una clase

Al usar una clase independiente en lugar de una clausura, resulta más fácil organizar el código. Crea una clase invocable que implemente el contrato `FeedGeneratorAlgorithm` y regístrala en `AppServiceProvider`.

```php theme={null}
// Crea en cualquier ubicación

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
    {
        // Por una restricción temporal de la API, se requiere autenticación
        $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);
```

## Autenticación

La funcionalidad de autenticación del starter kit oficial viene activada por defecto. Para desactivarla, pasa a `validateAuthUsing` una clausura que simplemente devuelva el DID del usuario.

```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>
  El feed se ve afectado por la "configuración de idioma" de la cuenta. Si el FeedGenerator recupera publicaciones pero el feed no aparece en Bluesky, revisa la configuración de idioma de la cuenta.
</Warning>

## Uso avanzado

Si usas comandos Artisan y programación de tareas para guardar las publicaciones en la base de datos y en el algoritmo solo consultas la BD, puedes lograr un feed rápido sin llamadas a la API.

```php theme={null}
// Ejemplo de algoritmo que devuelve el feed desde la BD

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}
// Ejemplo de programación de tareas para recopilar publicaciones (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

- [Testing](/es/packages/laravel-bluesky/testing.md)
- [BlueskyManager y HasShortHand](/es/packages/laravel-bluesky/bluesky-manager.md)
- [Tutorial de bots - Laravel Bluesky](/es/packages/laravel-bluesky/bot-tutorial.md)
- [Laravel Bluesky](/es/packages/laravel-bluesky/index.md)
- [Crypto — criptografía de AT Protocol](/es/packages/laravel-bluesky/crypto.md)
