Saltar al contenido principal

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.
Tutorial oficial: Crear un feed personalizado
Starter kit oficial: bluesky-social/feed-generator

Registro del algoritmo del FeedGenerator

La forma más sencilla es registrar el algoritmo como clausura en AppServiceProvider::boot().
// 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.
[
    '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).
Lo único que tienes que decidir es el name del FeedGenerator y su implementación.

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

Genera el comando

php artisan make:command PublishGeneratorCommand
2

Implementa el comando

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;
    }
}
3

Ejecuta el comando

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.

Crear varios FeedGenerator

Basta con cambiar el name y llamar a register varias veces para crear múltiples feeds.
// 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.
// 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.
// 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');
    }
}
// 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.
// 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');
});
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.

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.
// 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');
});
// Ejemplo de programación de tareas para recopilar publicaciones (routes/console.php)

use Illuminate\Support\Facades\Schedule;

Schedule::command('bluesky:collect-posts')->everyFiveMinutes();
Última modificación el 13 de julio de 2026