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.
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.
Genera el comando
php artisan make:command PublishGeneratorCommand
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;
}
}
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();