WG

Wendeson Gomes

Desenvolvedor Front-End

Como fiz um código que muda minha foto de perfil do BlueSky a cada vez que ganho um follow.

April 26, 2024 - 5 min read

Resumo

Estava tranquilo em casa quando ouvi falar dessa BlueSky e fui dar uma olhada, olhei e é praticamente o twitter, mas uma coisa que me chamou muito a atenção foi, você pode fazer tudo no site com codigo, fazer bot gratuitamente sem pagar api, postar, editar a foto, trocar o banner, dar like e etc, quando olhei isso pensei: "E se eu fizesse um loading na minha foto, que quando chegasse a 100 seguidores ele completasse a minha foto com o loading", e foi isso que eu fiz e vim mostrar para vocês!

O inicio - Conexão e Login

Nunca mexi muito com o nodejs puro, então ai já começou um desafio legal, mas a documentação e a api do BlueSky é bem intuitiva e completa. Começei instalando o @atproto/api que a documentação pede para a gente se conectar ao BlueSky, depois disso a gente Chama o BskyAgent depois literalmente é só fazer login na sua conta:

const agent = new BskyAgent({
  service: 'https://bsky.social'
})
await agent.login({
  identifier: 'User',
  password: 'Password'
})

Pegando o seu perfil

Agora a gente vai pegar a sua foto de perfil e continua sendo muito simples, coloque o username da sua conta para pode pegar o url da foto de perfil que está lá e sua quantidade de seguidores:

const { data } = await agent.getProfile({ actor: username })
const followers = data.followersCount
const avatar = data.avatar

Fazendo o SVG para o loading

agora chegamos aonde eu acho que é a parte mais complicada de explicar, fiz um svg aonde ele fica certinho com a imagem que vem do blueSky e precisamos fazer uns calculos para ficar carregando em cima da sua foto de perfil.

Primeiro Fazemos o calculo:

const QuantosSeguidoresQueQuerChegar = 100
const QuantosSeguidoresTemNoMomento = data.followersCount
const QuantoOLoadingVaiCarregando = ( 3018 / QuantosSeguidoresQueQuerChegar )  * QuantosSeguidoresTemNoMomento - 3018

O 3018 é um numero aonde o circulo fica fechado certinho, Pegamos o 3018 e dividimos pelo QuantosSeguidoresQueQuerChegar que dar: 30.18, multiplicamos por QuantosSeguidoresTemNoMomento: 30.18 * 7 = 211,26 e subritairmos pelo 3018 que fica: 2.806,74, para o circulo fechar a gente vai dimunuindo o 3018!

Agora fazemos o SVG da seguinte forma:

const svg = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000">
    <circle cx="500" cy="500" r="480" fill="none" stroke="#e11d48" stroke-width="40" stroke-dasharray="3018" stroke-dashoffset="${QuantoOLoadingVaiCarregando}" transform="rotate(-90 500 500)" /> 
</svg>
`;

Pegando o link da foto de perfil e transformando em buffer

Com o getProfile a gente pode pegar o link da foto de perfil simplesmente fazendo: data.avatar, agora a gente tem o link mas precisamos do arquivo então fiz do seguinte modo, com o Axios:

const input = (await axios({ url: data.avatar, responseType: "arraybuffer" })).data as Buffer

Juntando o SVG com a Imagem com Sharp e Atualizando a foto de Perfil.

pronto com isso podemos usar a foto de perfil no Sharp e fica desse modo:

// Colocamos a foto no sharp para ele reconhecer a imagem.
sharp(input)
  // Agora colocamos o SVG, e sempre tem q ser um buffer.
  .composite([{ input: Buffer.from(svg) }])
  // Ele salva a imagem, caso der um error ele vai para o if e mostra no console.
  .toFile('imagem_combinada.jpg', async (err) => {
    if (err) {
      console.error('Erro ao combinar a imagem e o SVG:', err);
      return;
    }
    // Vamos carregar essa imagem lá do lado do BlueSky, Não tem como enviar a foto direto pro upsertProfile
    const { data } = await agent.uploadBlob(readFileSync('imagem_combinada.jpg'), { encoding: "image/png" });

    // Agora vamos atualizar o perfil
    await agent.upsertProfile(async existingProfile => {
    // Verificando se o perfil existe
    const existing = existingProfile ?? {};
    // Vamos atualizar a imagem
    existing.avatar = data.blob;
    return existing;
  });
});

Verificando se tem Novo Follow.

Para verificar coloquei dentro de um while que verifica a quantidade de followers quando alterar ele atualiza a foto de perfil desse modo:

let verificarQuantidadeDeFollowrs = 0

while (true){
  verificarQuantidadeDeFollowrs = QuantosSeguidoresTemNoMomento
}

No final o codigo ficou desta forma:

import { BskyAgent } from '@atproto/api';
import { readFileSync } from 'fs';
import sharp from 'sharp';
import axios from 'axios';

async function VerifyAndChangeImage(){
  const username = 'User'
  const password = 'Password'
  const agent = new BskyAgent({
    service: 'https://bsky.social'
  });
  
  try {
    await agent.login({
      identifier: username,
      password: password
    });

    let verificarQuantidadeDeFollowrs = 0
    
    while (true) {
      const { data } = await agent.getProfile({ actor: username });
    
      if (data.followersCount === undefined) {
        console.log('Erro: Não foi possível obter o número de seguidores.');
        return;
      }

      if(verificarQuantidadeDeFollowrs !== data.followersCount){
        const QuantosSeguidoresQueQuerChegar = 100
        const QuantosSeguidoresTemNoMomento = data.followersCount
        const QuantoOLoadingVaiCarregando = ( 3018 / QuantosSeguidoresQueQuerChegar )  * QuantosSeguidoresTemNoMomento - 3018

        verificarQuantidadeDeFollowrs = QuantosSeguidoresTemNoMomento
    
        const svg = `
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000">
            <circle cx="500" cy="500" r="480" fill="none" stroke="#e11d48" stroke-width="40" stroke-dasharray="3018" stroke-dashoffset="${QuantoOLoadingVaiCarregando}" transform="rotate(-90 500 500)" /> 
          </svg>
        `;
    
        const input = (await axios({ url: data.avatar, responseType: "arraybuffer" })).data as Buffer;

        sharp(input)
        .composite([{ input: Buffer.from(svg) }])
        .toFile('imagem_combinada.jpg', async (err) => {
            if (err) {
              console.error('Erro ao combinar a imagem e o SVG:', err);
              return;
            }
          const { data } = await agent.uploadBlob(readFileSync('imagem_combinada.jpg'), { encoding: "image/png" });

          await agent.upsertProfile(async existingProfile => {
            const existing = existingProfile ?? {};
            existing.avatar = data.blob;
            return existing;
          });
        });
      }

      await new Promise(resolve => setTimeout(resolve, 15000));
      }
  } catch (error) {
    console.error(error);
  }
}

VerifyAndChangeImage()