Como postar no Bluesky posts com cards de preview de links
Olá a todos novamente,
Neste post, finalizarei a trilogia de artigos que estou escrevendo a respeito do desenvolvimento de um bot do Tabnews para o Bluesky. Para quem não pode acompanhar, segue os links para o primeiro post, a qual eu apresento o bot e o para o segundo post, a qual eu descrevo o processo de desenvolvimento do bot do Tabnews para o Bluesky. Para finalizar esta série, faltava verificar como postar um card com a imagem para mídias sociais como preview do link, para aumentar ainda mais o impacto dos posts que estão sendo anunciados pelo bot.
Ontem à noite, por causa das últimas notícias, decidi terminar esta pendência e consegui realizar o que desejava. Se você segue o nosso bot, deve ter notado postagens ontem à noite. Estava testando a implementação deste novo recurso, a qual implementei com êxito. Neste post, irei descrever como funciona o processo de adição de um card de preview para a postagem automatizada no Bluesky. Para isso, temos que seguir os seguintes passos, a quais vamos discorrer em tópicos neste post:
- Obtendo a URL da imagem a ser utilizada na thumbnail do card
- Fazendo o upload da imagem no Bluesky
- Criando a postagem junto com o card
A seguir, detalharemos os passos para fazer esta tarefa. Neste post detalharei as instruções em PHP, usando o pacote cjrasmussen/bluesky-api
, disponível no Composer.
Obtendo a URL da imagem a ser utilizada no card
Para isso, precisamos obter o link que é referenciada por uma tag especial, a qual define a imagem a ser exibida na thumbnail do card da postagem. Em uma situação normal, devemos procurar por uma tag meta
com o atributo name="twitter:image"
ou com o atributo property="og:image"
, e procurar pela url no valor do atributo content
.
Para isso, devemos usar alguma biblioteca de crawler da URL e fazer a busca pela tag, usando uma forma de seleção. Uma forma comunente utilizada pelas bibliotecas é pelos seletores XPath. Para encontrar estas tags e obter o valor da url da imagem, podemos utilizar os seguintes seletores XPath:
//meta[@name="twitter:image"]/@content
//meta[@property="og:image"]/@content
Mas, o Tabnews facilita a nossa vida, definindo uma forma única na API para encontrar a imagem de thumbnail para visualização nas redes sociais. Para isso, devemos formar a seguinte URL: https://www.tabnews.com.br/api/v1/contents/<author>/<slug>/thumbnail
, onde <author>
é o nome do autor e <slug>
é a slug da postagem, da mesma forma que vemos na URL de um post do Tabnews.
Sendo assim, não tem segredo encontrar a URL da imagem do thumbnail do Tabnews. Escrevi uma pequena função em PHP para abstrair esta geração:
function getTabnewsPostThumbnailUrl($author, $slug){
return 'https://www.tabnews.com.br/api/v1/contents/'.$author.'/'.$slug.'/thumbnail';
}
Com isso avançamos para a próxima etapa.
Fazendo o upload da imagem para o Bluesky
Antes de fazermos a postagem, precisamos enviar a imagem para o Bluesky e obter a URL da imagem no sistema do Bluesky, para que possamos ter o link interno da imagem para referenciar na criação da postagem. Para isso criei uma nova função para isso, passando dois argumentos: $connection
, que é o objeto de conexão ao Bluesky criado no ato de login do sistema, a qual detalhamos no post anterior e $url_image
, que é a URL da imagem da thumbnail da postagem.
A função que apresentamos aqui, que é uma ligeira variação deste artigo, chama a função do PHP file_get_contents
para pegar o conteúdo da imagem pela URL, pega o mimetype da imagem pelo header da requisição (algo que o Tabnews retorna na sua rota) - que é importante para informar ao Bluesky no ato de envio da imagem. Perceba que disparo um Error
em caso de ausência deste header, mas que pode ser contornado com auxílio de pacotes que detectam o mimetype pelos bytes da imagem (mime-detect para Javascript e PHP Mime Detector).
A seguir apresentamos a função que realiza este upload.
function uploadImageToBluesky($connection, $url_image){
$body = file_get_contents($url_image);
$headers = get_headers($url_image, 1);
if (isset($headers['Content-Type'])) {
$mime = $headers['Content-Type'];
} else {
throw new Error('Missing Content-Type on the url of the image');
}
$response = $connection->request('POST', 'com.atproto.repo.uploadBlob', [], $body, $mime);
$image = $response->blob;
return $image;
}
Bem, o que este código acima fez no Bluesky? Pelo que consta na documentação oficial do Bluesky, o retorno declarado na documentação é sucinto, apenas constando de um atributo denominado blob
, com as informações internas do Bluesky para encontrar a imagem. Segundo diz a documentação, se esta referência não for linkada a algum post, ela será removida.
A título de ilustração, reproduzimos abaixo o resultado de uma execução da função nativa print_r
para a variável $response
.
stdClass Object
(
[blob] => stdClass Object
(
[$type] => blob
[ref] => stdClass Object
(
[$link] => bafkreigtvn6lt6w6cmigtzckm7ibycywxeq4w5e5urdprk5srqubfmlsum
)
[mimeType] => image/png
[size] => 38442
)
)
Como podemos ver, o valor retornado é um link interno para esta imagem enviada para o Bluesky e as informações a respeito desta imagem.
Com a informação da imagem postada no Bluesky, podemos seguir para fazer a postagem, a qual vamos discorrer na próxima seção.
Criando a postagem junto com o card
Com a imagem enviada para o Bluesky, podemos fazer a postagem com o card do link da imagem. Antes disso, recaptulamos o código que usamos para declarar os argumentos para fazer a requisição da postagem, a qual apresentamos na postagem anterior. Lembre-se que consideramos o texto a ser enviado na variável $text
, a URL para ser incorporada como link $url
e as posições inicial e final do texto a ser considerado como link nas variáveis $startBytePos
e $endBytePos
.
$args = [
'collection' => 'app.bsky.feed.post',
'repo' => $bluesky->getAccountDid(),
'record' => [
'text' => $text,
'langs' => ['pt-br', 'en'],
'createdAt' => date('c'),
'$type' => 'app.bsky.feed.post',
'facets' => [
[
'index' => [
'byteStart' => $startBytePos,
'byteEnd' => $endBytePos,
],
'features' => [
[
'$type' => 'app.bsky.richtext.facet#link',
'uri' => $url,
]
]
]
]
],
];
Para colocarmos o card na postagem, precisamos declarar mais uma chave no array record
, de nome embed
, a qual declaramos os seguintes itens:
- Uma chave
$type
com o valor'app.bsky.embed.external'
; - A url a qual este card irá apontar, a qual podemos usar a mesma variável
$url
- mas que poderia ser qualquer outro link; - O título do card, a qual colocamos na variável
$title
; - A descrição do card, a qual podemos usar um texto de resumo. Para o bot, eu uso o valor retornado por este método reproduzido abaixo, a qual pegamos a slug da página e o autor para montar a requisição da API do Tabnews para pegar os detalhes do post, e procuro pelo valor retornado pela chave
body
e pego os primeiros 50 caracteres do texto da postagem. O retorno desta variável é usado na variável$description
.
function getDescriptionOfPostTabnews($author, $slug){
$url = 'https://www.tabnews.com.br/api/v1/contents/'.$author.'/'.$slug;
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl);
curl_close($curl);
if ($response) {
$data = json_decode($response, true);
return substr($data['body'], 0, 50) . "...";
} else {
throw new Error('Error on call API');
}
}
- Por último, pegamos a variável gerada pela biblioteca do Bluesky e retornada pela função que apresentamos na seção anterior e colocamos na variável
$image
.
Com estas informações, adicionamos a chave embed
a chave record
da variável $args
para adicionar as informações do card às informações da postagem. O restante do código da postagem é apresentado neste excerto abaixo.
$args['record']['embed'] = [
'$type' => 'app.bsky.embed.external',
'external' => [
'uri' => $url,
'title' => $title,
'description' => $description,
'thumb' => $image,
],
]
$data = $bluesky->request('POST', 'com.atproto.repo.createRecord', $args);
Se tudo der certo, a postagem virá além do texto e do link, terá um card com uma imagem elegante apontando para o link, como você pode ver neste post do Bluesky.
Conclusões
Com isso, concluímos a nossa trilogia de postagens a respeito do Bot do Tabnews para o Bluesky. Foi uma experiência muito interessante lidar com uma nova abordagem de API e atestar, que apesar de algumas dificuldades, o Bluesky possui uma documentação muito interessante, e podemos considerar a developer experience do Bluesky como muito interessante. Levarei o conhecimento acumulado nesta experiência para desafios futuros.
Foi um prazer enorme fazer este desafio. Agradeço a receptividade da comunidade do Tabnews durante toda esta jornada. Por fim, convido a você que ainda pretende usar o Bluesky a seguir nosso bot. Nosso handle é tabnewsbot.bsky.social
e você pode acessar aqui.
Até a próxima!