joão dutra PortuguêsEnglish

Projetos

Três coisas que construí, com o problema que motivou cada uma, a decisão que tomei e o que eu faria diferente hoje.

Bananablet

MV3 · Cloudflare Workers · R2 · Supabase · VPS

O Bananablet é uma extensão de navegador que adiciona funcionalidade a um cliente de jogo online. Comecei porque o jogo não tem API, não tem SDK e não oferece nenhum ponto de extensão oficial: quem quiser construir algo em cima dele precisa interceptar o WebSocket binário que o cliente usa para falar com o servidor. Está no ar desde novembro de 2025, com mais de 4.000 instalações e 1.9k pessoas usando por mês.

É projeto meu, feito sozinho, e o único dos três que não saiu de um pedido de cliente.

Tecnologias

O que deu trabalho

Uma atualização do jogo passou a sortear os identificadores de pacote a cada conexão, derivando cada um de um hash sobre um desafio que o servidor manda no bootstrap. Isso quebrou 115 pontos do código de uma vez. Dava para remapear os valores em toda release, mas escolhi replicar a função de derivação do próprio cliente: refatorei 124 pontos e passei a cobrir cerca de 147 identificadores sem precisar de manutenção. O preço é ficar acoplado a um detalhe interno do jogo, e se eles trocarem o algoritmo eu descubro de novo do zero.

Medindo o Worker, encontrei 707 mil requisições em 24 horas, e quase tudo vinha da rota mais barata que eu tinha: cada cliente perguntando de tempos em tempos quem estava online. Migrei presença para uma conexão WebSocket persistente, e o tráfego caiu cerca de 95%.

Devia ter instrumentado antes. Só descobri isso numa investigação pontual, uma semana depois de o sistema já estar no ar, e com telemetria por rota desde o começo eu teria visto sozinho.

Ver na Chrome Web Store

Try-On Virtual — Ótica Qualidade Visual

Node · TensorFlow.js · Gemini · Docker · Traefik

A Qualidade Visual é uma ótica aqui de Juazeiro do Norte, e o que eles queriam era simples de descrever e chato de resolver: o cliente vendo o óculos no próprio rosto antes de comprar, na página do produto, sem instalar nada e sem sair do fluxo de compra.

O problema é que a chamada ao Gemini que gera a imagem custa caro, demora, e às vezes volta com o rosto errado ou a armação torta. O caminho óbvio seria gerar, olhar e tentar de novo quando não presta, mas aí a espera multiplica pelo número de tentativas.

Tecnologias

O que deu trabalho

Em vez de tentar em sequência, disparo várias gerações ao mesmo tempo e valido todas em paralelo, comparando cada candidata com a foto original por um embedding facial. Vence a de menor distância. Pago sempre por todas, inclusive quando a primeira já estava boa, mas a espera do usuário para de crescer com o número de tentativas.

Navegar o catálogo dentro do widget significava consultar a API da loja a cada interação, então construí três camadas de cache em memória, mais cache de imagem em disco. Medido em produção, o conjunto acerta 80,5% sobre 9.994 consultas.

Nessas mesmas medições apareceu algo que o desenho não previa: uma das três camadas nunca é acionada, porque a de busca sempre responde antes dela. Duas dão conta do mesmo trabalho. Foi medindo que eu descobri isso, não lendo o código, e é o tipo de coisa que só aparece com número na frente.

Ver a loja da Qualidade Visual

Feeds imobiliários — Alta Performance

PHP · WordPress · MySQL · XML

A Alta Performance é uma imobiliária que já tinha o acervo dentro do WordPress e precisava publicá-lo no Chaves na Mão, no Imovelweb e no VRSync, que é o feed do grupo OLX e alimenta o Vivareal e o ZAP. Cada um exige um arquivo XML no formato dele.

Gerar XML é a parte fácil. O difícil é que os três descrevem o mesmo imóvel de formas incompatíveis, e um imóvel válido para um volta rejeitado do outro com um erro genérico que nunca diz qual campo está errado.

Tecnologias

O que deu trabalho

Os três falam vocabulários diferentes para a mesma coisa. O Chaves na Mão usa termos em português e exige 48 tags escritas sempre, mesmo vazias. O Imovelweb, cuja API é a OpenNavent, manda tudo em CDATA, com a data em epoch de milissegundos e a operação de preço em espanhol. O VRSync é o único com namespace e XSD, usa vocabulário em inglês, guarda a semântica nos atributos em vez dos elementos, e quer o CEP só com dígitos, ao contrário do Imovelweb. Uma classe abstrata cuida do que é comum e cada portal implementa só a montagem do próprio documento, com um validador de campos obrigatórios por portal. Assim o log diz exatamente o que faltou em cada um, e "o portal rejeitou" vira uma lista acionável.

A importação de anúncios de parceiros trazia 20 a 30 fotos por imóvel, o que estoura qualquer limite de execução num WordPress compartilhado. A saída comum é aumentar os limites, e não há uma única chamada dessas no plugin: o feed é baixado uma vez e fatiado em janelas de 50 itens, e o navegador encadeia as requisições, então cada processo PHP fica curto.

Os anúncios chegavam ao portal com a descrição virando uma parede de texto, embora estivesse correta no WordPress. Capturei o feed real que estava saindo e reproduzi a função de normalização fora do WordPress, o que transformou "o portal exibe errado" num teste de trinta linhas. Se eu tivesse escrito teste de fixture nos geradores desde o começo, teria pego esse e mais um antes do portal pegar.

Ver o site da Alta Performance