En pleno éxodo de Twitter a Bluesky llega también la necesidad de usar correctamente su API si queremos, por ejemplo, poder automatizar mensajes. En mi caso, la necesidad viene como casi siempre, de unir mis dos actividades principales: hablar sobre cine y el desarrollo de aplicaciones. Formo parte del jurado Flipesci, donde votamos cada película que vemos y uno de mis compañeros me propuso la idea de que en nuestra nueva cuenta de Bluesky se publicara un log cada vez que alguien valorase una película. Algo con esta pinta:
La web donde se vota y se almacenan las notas están en PHP así que se trata de utilizar la API de Bluesky desde un script en PHP. No he visto demasiada documentación para este lenguaje, quizá cuando estés leyendo esto sí que haya algo más pero ahora mismo está más orientado a lenguajes más de moda como Python y otros. Más abajo os dejo las funciones de autenticación y de escritura por si te son de ayuda.
Te ahorro detalles de la lógica relativa a la construcción del texto en sí, que ya es algo muy específico de mi caso, pero a grandes rasgos, para evitar cargar el proceso de votación lo que hago es marcar las notas que están pendientes de enviar y después una tarea cron cada 15 minutos revisa cuáles hay que enviar, construye el texto con los datos de la base de datos y realiza los envíos correspondientes. Lo he limitado a máximo 3 post cada vez, por si hay un usuario que ha valorado muchas películas de un golpe ya que no interesa saturar el timeline.
El script PHP
Lo primero fue entender cómo usar la API de Bluesky. Esta requiere autenticación y permite publicar a través del método com.atproto.repo.createRecord. La clave aquí es configurar correctamente las credenciales de tu cuenta y el formato del mensaje. Para interactuar con la API de Bluesky, necesitas conseguir dos elementos clave: una contraseña de aplicación y tu código DID. Para la contraseña de aplicación, inicia sesión en tu cuenta de Bluesky, ve a la sección de configuración de tu perfil y selecciona «Contraseñas de aplicación». Crea una nueva contraseña y guárdala en un lugar seguro, ya que no podrás volver a verla después de generarla. Puedes consultar tu código DID desde la configuración avanzada de tu perfil en Bluesky, donde se muestra como un identificador único de tu cuenta. Ambos son necesarios para que tu script funcione correctamente.
Aquí te comparto la clase con las funciones para autenticarte y publicar en Bluesky:
class Bluesky{
private $handle = 'tu.handle.blueskay';
private $appPassword = 'tu-contraseña-de-aplicación';
private $did = "did:... tú codigo DID";
private $jwt;
function blueskyAuth() {
$url = 'https://bsky.social/xrpc/com.atproto.server.createSession';
$data = json_encode([
'identifier' => $this->handle,
'password' => $this->appPassword
]);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($ch);
if (!$response) {
die('Error en la autenticación: ' . curl_error($ch));
}
$responseData = json_decode($response, true);
curl_close($ch);
if (isset($responseData['accessJwt'])) {
$this->jwt = $responseData['accessJwt'];
} else {
die('Error en la autenticación: ' . $responseData['error'] ?? 'Desconocido');
}
}
// Publicar un post en Bluesky
function postToBluesky($text, $url) {
$urlAPI = 'https://bsky.social/xrpc/com.atproto.repo.createRecord';
$start = strpos($text, $url);
$end = $start + strlen($url);
$facets = [
[
"features" => [
[
'$type' => "app.bsky.richtext.facet#link",
"uri" => $url
]
],
"index" => [
"byteStart" => $start,
"byteEnd" => $end
]
]
];
$data = json_encode([
'collection' => 'app.bsky.feed.post',
'repo' => $this->did,
'record' => [
'text' => $text,
'facets' => $facets,
'createdAt' => date('c'),
'$type' => 'app.bsky.feed.post'
]
]);
$ch = curl_init($urlAPI);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $this->jwt
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($ch);
if (!$response) {
die('Error al publicar: ' . curl_error($ch));
}
$responseData = json_decode($response, true);
curl_close($ch);
return $responseData;
}
}?
Algunas consideraciones
Las URL. Que las URLs sean clicables en el post no es algo automático como había pensado inicialmente. En la primera prueba la url aparecía como texto plano. Por eso he incluido en el código el parámetro url (en mi versión siempre va con url, si en la vuestra no, tendréis que cambiarlo a opcional). Necesitas usar las facetas (facets) en el mensaje JSON. Está ya incluida en el código que os he pasado. Es decir, no solo incluyes la url en el texto sino que le indicas estructuralmente que esa parte de tu post es una url.
Cuándo y cómo. No cargues procesos críticos como es en mi caso el registro de notas. Es mejor separar las tareas y ejecutarlas de manera asíncrona con cron jobs. Así además puedes controlar la frecuencia de publicación.
Clases existentes. Podéis usar clases ya existentes como cjrasmussen/bluesky-api. Esto sería lo más fácil y la podéis cargar por Composer. Hace más sencillo sobre todo la parte de auth. Yo he preferido usar mi propio código para no depender de algo externo y porque era algo muy sencillo que no me generaba mucho más trabajo. Te invito a echarle un ojo a lo que hay porque quizá para tus necesidades sea más útil. Igualmente tendrás que manejar algunas de las cuestiones que he comentado (la manera de construir el mensaje, etc.).
Espero que te haya sido de ayuda. ¿Has probado algo similar? ¿Tienes alguna duda o mejora? ¡Déjalo en los comentarios!