Programando bots para o Mastodon: identificando instâncias brasileiras

Hoje veremos como criar scripts que buscam as informações de identificação de uma instância, e de que forma é possível identificar se ela é brasileira – ou, no mínimo, se suas informações estão em português.

O meu script de censo do Fediverso BR é executado aproximadamente uma vez por ano. Ele percorre as dezenas de milhares (mais de 100.000, neste momento) de servidores federados, buscando identificar quais correspondem a instâncias brasileiras, e quais as tendências em relação a elas.

Eu resisto a compartilhá-lo, porque como envolve mais de 100.000 servidores, eu não quero contribuir gerar ainda mais demandas e custos aos seus admins, ao facilitar que um monte de gente rode scripts que os contatem indiscriminadamente.

Compartilhando só a lógica central

Neste final de semana eu estou rodando essa rotina novamente (a vez anterior foi em fevereiro de 2025), e o Josir Gomes voltou a me pedir pra compartilhar o script. Eu tenho certeza de que a intenção dele é boa, pois sei que ele é pesquisador sobre esse tema, e que seu interesse (assim como o de outros pesquisadores e interessados) é legítimo.


Uma lupa ampliando um fragmento do símbolo do Fediverso

Por isso,decidi compartilhar (só) o filé mignon do meu script: as rotinas que obtém os dados sobre um servidor, e a descrição do método que uso para identificar se são brasileiros (ou lusófonos, dependendo do critério). Ao final, faço um pedido especial de responsabilidade ao usar as técnicas aqui descritas.

Importante: Para rodar os scripts abaixo, você precisa ter uma shell Bash ou compatível, com o awk e o gron (para facilitar o consumo das respostas em formato JSON), instalados em um sistema compatível com o Posix (como a maior parte das distribuições Linux, o Mac e Unix em geral). Não incluirei as explicações detalhadas, mas todos os mecanismos que usei hoje já estão explicados no post anterior: “Programando bots para o Mastodon: listando usuários via API” (e nos seus antecessores)

Hoje veremos um método baseado no ActivityPub, e outro que usa especificamente a API do Mastodon.

Quase todos os serviços on-line interativos conectados ao Fediverso respondem a chamadas básicas de identificação "NodeInfo", que estão definidas no protocolo ActivityPub, e um grande número deles também responde a chamadas bem mais detalhadas que fazem parte do protocolo Mastodon (com o qual vários outros serviços são compatíveis).

Veremos hoje como identificar um servidor a partir de cada uma das duas chamadas de identificação acima: a básica (do ActivityPub) e a detalhada (do Mastodon e seus compatíveis).

Usarei a minha própria instância (arram.senta-la.cloud) nos exemplos, mas você pode substituir pela que desejar consultar.

Identificação básica usando o ActivityPub

Como dito, o tipo de identificação "NodeInfo" funciona na grande maioria dos servidores interativos conectados ao Fediverso (incluindo Mastodon, Misskey, Lemmy, GoToSocial, Snac, Ghost, WordPress e mais), e ela tem 2 passos: (1) obter qual a URL de informações do servidor; e (2) obter as informações do servidor.

Passo 1. Para obter a URL de informações do servidor, é necessário consultar a sua url /.well-known/nodeinfo, como no exemplo:

gron "https://arram.senta-la.cloud/.well-known/nodeinfo"
json = {};
json.links = [];
json.links[0] = {};
json.links[0].href = "https://arram.senta-la.cloud/nodeinfo/2.0";
json.links[0].rel = "http://nodeinfo.diaspora.software/ns/schema/2.0";

Na listagem acima, o comando que eu digitei na shell interativa está em negrito, e as demais linhas são a resposta recebida. A linha que nos interessa é a penúltima, que define o href do link.

No exemplo acima, portanto, descobrimos que a URL de informações do servidor é https://arram.senta-la.cloud/nodeinfo/2.0. Agora que a conhecemos, podemos consultá-la também:

gron "https://arram.senta-la.cloud/nodeinfo/2.0"
json = {};
json.metadata = {};
json.metadata.nodeDescription = "arram.senta-la.cloud - A instância das trends, tags, efemérides e estatísticas no Fediverso BR. Bem-vindos à federação!";
json.metadata.nodeName = "Arram, senta lá, Cloud!";
json.openRegistrations = false;
json.protocols = [];
json.protocols[0] = "activitypub";
(...)

Novamente, o comando que eu digitei na shell interativa está em negrito, e as demais linhas são o início da resposta recebida, que eu cortei pois é extensa.

Note que as linhas retornadas apresentam várias informações sobre a minha instância, que roda o serviço Mastodon.

Para outros serviços (mesmo que sejam compatíveis com o Mastodon), os nomes dos atributos serão os mesmos, mas podem ser retornadas outros atributos (a menos, ou a mais) – por exemplo, se você inspecionar uma instância PeerTube, ela também retornará os formatos e resoluções de vídeo que ela suporta, e se você consultar uma instância Sharkey, ela retornará também o contato de seu admin (que o Mastodon não retorna nessa chamada).

O script a seguir realiza as duas consultas à minha instância e mostra o conteúdo de alguns atributos selecionados (que não necessariamente todas as instâncias retornarão ao responder a essa chamada):


#!/usr/bin/env bash
#
# instinfo_AP.sh - obtém via ActivityPub informações sobre uma instância
#
# Copyright (c) 2026,  Augusto Campos (https://augustocampos.net/).
# Licensed under the Apache License, Version 2.0.
#

inst="https://arram.senta-la.cloud"
INFO_URL=$(curl -sL "$inst/.well-known/nodeinfo" | 
	gron | awk -F' = ' '/href/ {print $2}' | tr -d '";' | head -n 1)
	
curl -sL "$INFO_URL" | gron | awk -F' = ' '
/[.]nodeName/ {print "nome",$2}
/[.]nodeDescription/ && !/rules/ {print "descrição",$2}
/[.]software[.]name/ {print "sistema",$2}
/[.]software[.]version/ {print "versão",$2}
/[.]usage[.]users[.]total/ {print "usuários cadastrados",$2}
/[.]usage[.]users[.]activeMonth/ {print "usuários ativos",$2}
/[.]usage[.]localPosts/ {print "posts",$2}
'

Note que no script acima são feitos dois acessos ao servidor: o primeiro é para descobrir qual a URL de informações, e o segundo para consultá-la, retornando a seguir o conteúdo de alguns de seus atributos: o nome e descrição da instância, o nome e versão do software que está rodando nela, e algumas estatísticas básicas.

Nem todo servidor responde a essa chamada, mas ela funciona com a ampla maioria deles.

Se desejar, você pode modificar o script para tornar parametrizável, suportar mais (ou outros) atributos, e incluir tratamento de erro, por exemplo.

Identificação mais ampla usando a API do Mastodon

Além do próprio Mastodon, vários outros serviços do Fediverso (como o Snac, GoToSocial, WordPress e muitos outros) respondem a chamadas da API do Mastodon, de uma forma similar à que vimos acima, mas às vezes retornando mais informações.

Diferente do caso que vimos acima, fazer a consulta pela API do Mastodon envolve apenas uma chamada ao servidor, pela sua URL /api/v1/instance, como no exemplo:

gron "https://arram.senta-la.cloud/api/v1/instance"
json = {};
json.approval_required = false;
json.short_description = "arram.senta-la.cloud - A instância das trends, tags, efemérides e estatísticas no Fediverso BR. Bem-vindos à federação!";
json.stats = {};
json.stats.domain_count = 11027;
json.stats.status_count = 15863;
json.stats.user_count = 9;
json.title = "Arram, senta lá, Cloud!";
json.uri = "arram.senta-la.cloud";
json.version = "4.5.4";

Mais uma vez, o comando que eu digitei na shell interativa está em negrito, e as demais linhas são partes da resposta recebida, que eu cortei pois é extensa.

Ao fazer essa chamada a um servidor Mastodon, a resposta trará várias dezenas de atributos, descrevendo a instância, todas as suas regras, os detalhes sobre a conta de seu admin, a sua política quanto a aceitar ou não novas contas, quantos caracteres podem ter os seus posts, quantas alternativas podem ter as suas enquetes, e mais.

Novamente, ao fazermos a mesma chamada a outros tipos de servidores (compatíveis, mas que não estejam rodando o software Mastodon), os atributos terão os mesmos nomes, mas o conjunto pode ser diferente – por exemplo, uma instância Sharkey pode não retornar detalhes sobre seu admin, e uma instância Snac pode não listar suas regras.

O script a seguir realiza a consulta à minha instância e mostra o conteúdo de alguns atributos selecionados (que não necessariamente todas as instâncias compatíveis retornarão ao responder a essa chamada):


#!/usr/bin/env bash
#
# instinfo_MAS.sh - obtém via API Mastodon informações sobre uma instância
#
# Copyright (c) 2026,  Augusto Campos (https://augustocampos.net/).
# Licensed under the Apache License, Version 2.0.

inst="https://arram.senta-la.cloud"
curl -sL "$inst/api/v1/instance" | gron | awk -F" = " '
	/[.]title/ { print "título",$2 }
	/[.](short_)?description/ { print "descrição",$2 }
	/[.]languages\[/ { print "idioma",$2 }
	/[.]contact_account[.]display_name/ { print "admin - nome",$2 }
	/[.]contact_account[.]note/ { print "admin - nota",$2 }
	/[.]contact_account[.]url/ { print "admin - url",$2 }
	/[.]rules\[[0-9]+\][.](text|hint)/ {
		gsub(/[";]/, "", $2);
		regras = regras "  - " $2
	}
	END { if (regras) print "regras",regras }
'

O script consulta a instância e retorna o conteúdo de alguns de seus atributos: o título e descrição da instância, idiomas visíveis na API, alguns detalhes sobre o admin, e o texto das regras (sem formatação).

Esses atributos foram escolhidos porque demonstram (se você testar com outras instâncias) que a resposta de outros serviços são diferentes das do Mastodon: por exemplo, testei com uma instância Misskey e, da lista acima, ela retornou apenas título e descrição. Testei com Snac e veio quase tudo, mas não as regras. E assim por diante.

Só os servidores compatíveis com a API do Mastodon (versão 1) respondem a essa chamada.

Se desejar, você pode modificar o script para tornar parametrizável, suportar mais (ou outros) atributos, formatar texto e incluir tratamento de erro, por exemplo.

E como identificar se o servidor é brasileiro?

Dependendo do seu interesse, essa pode ser a pergunta de um milhão de dólares, e infelizmente não tem resposta direta, porque depende inclusive de definir o que significa o servidor ser brasileiro: é estar hospedado no Brasil? Ter admin brasileiro? Ter maioria de usuários brasileiros? Ter maioria de posts originados em português?

A minha abordagem geral para essa pergunta é identificar se o servidor ou a conta do seu admin são descritos em português, e aí (caso eu esteja procurando por servidores brasileiros, e não mais amplamente por servidores lusófonos) excluir da lista os servidores que possam ser identificados como sendo de outros países.

Dependendo do caso, os próprios protocolos oferecem informações sobre o idioma dos textos - no exemplo acima da chamada da API do Mastodon, eu incluí o acesso ao atributo "language", inclusive. Infelizmente isso ajuda pouco, porque é muito frequente essa informação estar errada ou incompleta - e ela não distingue entre o português brasileiro, o europeu e o da África.

Para lidar com isso, eu uso uma heurística, ou regra de padeiro, em que reúno todos os textos retornados pela API nas chamadas acima (a descrição da instância, suas regras, a mensagem sobre autorização pra criar novas contas, os detalhes da conta do admin, e até as URLs), e procuro neles algumas caraterísticas comuns nesses textos quando se trata de instâncias brasileiras, como por exemplo:

  • endereços que terminem com .br
  • menções a Brasil ou Brazil, a português ou a portuguese, ou a pt_BR ou 🇧🇷.
  • palavras como instância, comunidade, bem-vind, moderad, proibid, língua, regra, saiba
  • palavras como você, aqui, também, não
  • sufixos como ão, ões, inho, inha, ça, ço

Aplicar esse filtro é bastante eficaz, mas traz junto vários falsos positivos em italiano, francês, alemão, inglês, espanhol e galego (sendo que este último às vezes eu não desejo excluir, porque é uma língua tão próxima da nossa).

Antes de inspecionar manualmente, eu aplico um filtro que é eficaz para identificar a maior parte dos falsos positivos (i. e. servidores não-lusófonos identificados como brasileiros):

  • palavras como cuenta, (y|el|los|las), de la, de lo, (un|el) servidor, serán, ningún
  • palavras como información, hispano, contenido, habla, bienvenid, aplicables, proyecto
  • palavras como kommen kontakt, réseau, communiquez, votre
  • palavras como avéc, vous, lo, di, uno, unha, zum, wie, und, ein, zu, von

Note que as listas de palavras acima foram geradas empiricamente e a partir das estatísticas dos servidores reais do Fediverso, e podem não se aplicar a outros domínios.

Um pedido especial

A partir dos 2 scripts acima, e da heurística de identificação de origem/idioma, é possível implementar sistemas de coleta e análise de dados que responderão a questões interessantes sobre o Fediverso, e é por essa razão que eu os compartilho.

Porém, o Fediverso é composto de várias dezenas de milhares de servidores, muitos dos quais mantidos por voluntários que incorrem em custos de CPU e de tráfego. Respeite-os, não gere rotinas que os consultem com frequência exagerada, que os demandem de forma que os onere, e muito menos que tentem obter deles dados em desacordo com as suas políticas, termos e expectativas de privacidade.

Para saber mais sobre as ferramentas e técnicas aqui mencionadas, consulte os posts da tag Mastodon do meu blog.

Conversas boas são jogos de frescobol, não de tênis

Uma conversa leve é como um jogo de frescobol: o objetivo é manter a bolinha no ar, contribuindo para não fazer a outra pessoa derrubá-la.

Observando a chegada de várias centenas de novas pessoas a uma rede social desde a virada do ano, notei a repetição de padrões que já vi acontecerem, muitas vezes antes, nas tentativas de se enturmar.

Sou um observador de técnicas de conversação, pois o TEA sempre me demanda atenção extra à comunicação.

Alguns desses padrões funcionam, outros não - e eu observo isso do ponto de vista de alguém sempre atento a esse tema, pois o TEA me demanda atenção extra à comunicação.

O negócio é que uma conversa realmente boa é similar a uma partida de frescobol, em que lançamos a nossa contribuição de tal forma que o outro participante possa lançar também a dele, na expectativa de continuidade, com contribuição de ambas as partes, que não são adversárias entre si.



Assim, tenho duas sugestões a quem quer se enturmar e ter boas conversas nas redes (e, igualmente, fora delas):

1. Ao responder, comece concordando ou discordando explicitamente. Ideal: comece com "Sim! Além disso…"1

2. Pra puxar conversa, não responda a uma afirmação com uma pergunta. Exponha uma posição, e deixe as outras pessoas livres sobre como prosseguir. ESPECIALMENTE se estiver fazendo uma tentativa de humor.

Tende a dar mais certo se você deixar a outra pessoa livre para interagir como preferir (sem tentar colocá-la numa posição em que ela esteja sendo entrevistada ou chamada a confirmar, justificar ou refutar algo).

Pra manter a analogia, lembre-se que é completamente diferente de um debate de oponentes, que é como uma partida de tênis em que cada lado deseja lançar seu argumento para a quadra oposta, na expectativa de que o oponente não consiga lançar outro de volta.

Assim, se você perceber que está levantando a bola para o outro cortar contra você, aí a técnica simplesmente não funciona, e não vale a pena insistir nela, nem mencioná-la – e talvez nem seja possível ter uma conversa agradável sobre esse assunto, com essa pessoa, nesse momento.

 
  1.  “Sim, e...”, é uma dica extraída da comédia de improvisação (ou improv), que sugere que, para manter a cena rodando, um participante deve aceitar o que outro improvisador declarou (“sim”) e, em seguida, expandir a sua linha de pensamento (“e...”).

Loteria não é investimento

Esses caras que gastaram milhões em bilhetes da mega da virada se achando espertões, na intenção de ganhar um bilhão – e no fim das contas fizeram só meia dúzia de quadras –, sublinham mais uma vez: jogo não é investimento.

Eu ocasionalmente jogo na mega, mas… é jogo. As probabilidades estão totalmente contra mim. A chance de ganhar o maior prêmio é parecida com a de jogar pro alto o valor do bilhete, e cair uma fortuna no meu pé.

Inclusive é chato estar em bolão com gente que acha que está investindo.

Iniciando 2026 com uma leveza em especial

No primeiro dia do ano eu abri as caixas dos papeis que eram do meu pai, que se foi há pouco mais de 5 anos, pra separar o que ainda precisa ser guardado (por valor pessoal) e o que já podia ser descartado (por ter sido guardado por eventual razão tributária).

Certeza que foi a coisa mais difícil e extenuante que fiz nos últimos meses. Fiquei totalmente exausto, e precisei tomar um longo banho pra esvaziar até os pensamentos.

Mas antes do banho, levei tudo pro ponto de reciclagem, e abri espaço pro ano assim poder começar mais leve. Estou satisfeito.

Resolução de ano novo

O natal me trouxe um monitor maior, e pro ano novo eu troquei a configuração (e as teclinhas) do teclado, e mudei o Plex pelo Emby como solução pra clips, shows, séries e filmes.

Estamos no último domingo antes da virada de 2025 para 2026, e a minha estação de trabalho hoje está assim:


Foto do meu ambiente de trabalho, com 2 monitores lado a lado, uma telinha menor acima de um deles tocando um clip de rock dos anos 80, e um teclado de 84 teclas com teclinhas em uma combinação de cores chamada Rome. Ao lado, uma garrafa de água e um elefante de pelúcia.
Meu ambiente de trabalho no final de dezembro de 2025

Já faz 20 anos que eu uso 2 monitores lado a lado, e a novidade principal veio como meu presente de natal pra mim mesmo: o monitor maiorzão, à esquerda, um LG 32UR500 com resolução de 3840x2160, porque trabalho com cada vez mais janelas abertas, ao contrário do que desejaria.

A terceira tela, bem menor e acima, não fica conectada ao mesmo computador que as outras duas. Ela é independente, e serve apenas para ficar tocando clips e shows musicais enquanto eu trabalho.

Essa telinha menor também tem novidade, porque após passar vários anos usando o Plex (nela e no servidor de mídia da casa), aproveitei a virada do ano pra fazer a troca do Plex pelo Emby, e estou satisfeitíssimo – coisa que já há algum tempo eu não podia dizer sobre o Plex, que vinha ficando cada vez mais antipático.


Print de uma coleção de mídia chamada Séries Platinum, contendo 26 itens, com nomes como Wednesday, Taken, Pernalonga, Mad About You e Derry Girls
Print de uma tela do Emby mostrando uma coleção de séries

Eu testei Emby, Jellyfin e Kodi, e o Emby foi o que chegou mais perto do ponto de equilíbrio que eu curto entre flexibilidade e facilidade, e de ter suporte nativo nas plataformas em que eu quero usar pra assistir.

Migrar minha biblioteca de shows, clips, séries e filmes do Plex para o Emby foi bem fácil, e aproveitei pra reorganizar algumas coisas na coleção. Uma coisa que eu consegui fazer com facilidade no Emby e não conseguia no Plex1, foi colocar um script na crontab pra, todos os dias às 4 da manhã, gerar uma playlist com 200 clips da minha coleção (a coleção tem 900+ clips), selecionados aleatoriamente entre os 500 que estão há mais tempo sem serem assistidos, para ter diariamente uma programação variada.


Um jogo de teclas chamado Rome, com teclas em cor preta, e algumas em cor creme e cor vinho, gerando um contraste meio vintage. As teclas estão soltas e espalhadas, em ordem, sobre uma mesa.
Minhas teclinhas novas para 2026

Aproveitei a onda de mudanças e troquei o conjunto de teclas do meu teclado do dia a dia: agora instalei um conjunto chamado Rome, que é vintage na aparência, e muito confortável pra digitar.

Já está tudo definido: por aqui a resolução do ano novo será 3840x2160!

 
  1.  O Plex oferecia um recurso parecido nas suas smart playlists, mas não era exatamente o que eu queria.

Programando bots para o Mastodon: listando usuários via API

A API do Mastodon é rica, e desenvolver clientes ou robôs para interagir com ela está ao seu alcance em várias linguagens, como veremos em mais um exemplo.

Dando sequência aos exemplos de interação direta com a API do Mastodon que venho publicando, hoje veremos como obter a lista de usuários de uma instância, juntamente com dados sobre a sua atividade e identidade.

O ensejo para escrever este post foi o interesse em gerar esta lista das contas mais falantes da instância brasileira organica.social, que a partir do início da semana passada recebeu uma migração de 700+ brasileiros, graças especialmente a uma ação bem-sucedida da Biana na rede Bluesky.

Para rodar os exemplos abaixo, você precisa ter uma shell Bash ou compatível, com o awk e o gron (para facilitar o consumo das respostas em formato JSON), instalados em um sistema compatível com o Posix (como a maior parte das distribuições Linux, o Mac e Unix em geral).


foto de um brinquedo robô transformer em forma de elefante

Iremos direto ao ponto, e para uma explicação mais detalhada sobre alguns conceitos, sugiro a leitura também do post Programando bots para o Mastodon: obtendo dados via API.

Encaminhando consultas para a API do Mastodon

Já vimos que a interface básica com uma instância do Mastodon é por meio de uma interface (a API) acessada por meio de URLs, e que responde em formato JSON. Há uma série de bibliotecas que intermediam esse acesso em várias linguagens, mas faremos o acesso diretamente, para demonstrar o funcionamento.

Nosso exemplo de hoje é baseado em um endpoint específico da API: o /api/v1/directory, que na minha instância fica visível pela URL https://arram.senta-la.cloud/api/v1/directory - se você acessá-la em seu navegador, verá uma longa linha contendo a lista dos usuários conhecidos pela instância.

Com o acesso básico (da URL acima), a resposta colocará os usuários em ordem de quem postou mais recentemente, e mostrará apenas os primeiros 40 usuários que a instância conhece – ou seja, os usuários que postam em alguma timeline, lista ou tag seguida pelos usuários da própria instância.

A URL também pode incluir parâmetros, como:

  • local=true - ao invés de listar todos os usuários que a instância conhece, lista apenas os usuários da própria instância.
  • limit=60 - ao invés de retornar uma lista com até 40 contas, retornará até 60 (o máximo é 80)
  • offset=120 - ao invés de começar a listar a partir do 1º usuário, começa do 120º usuário.

Embora só possamos receber 80 contas por vez, note que com uma sequência de chamadas usando limit=80 e variando o parâmetro offset, podemos listar quantos quisermos: a primeira será com offset=0 (que é o default), a segunda com offset=80, depois 160, e assim por diante.

Vamos a 2 exemplos de URLs com parâmetros:

  • https://arram.senta-la.cloud/api/v1/directory?local=true - Retorna uma longa linha contendo apenas os usuários locais da minha instância (que é pequena, então será uma lista de meia-dúzia de contas).
  • https://arram.senta-la.cloud/api/v1/directory?limit=80&offset=160 - retorna uma longa linha contendo o equivalente à terceira página (considerando que cada página são 80 contas) da lista de usuários conhecidos pela instância.

Entendendo as respostas da API

Essas longas linhas da resposta são difíceis de compreender visualmente, porque usam o formato JSON, compatível com uma série de sistemas, mas de baixa legibilidade humana direta.

Há várias formas de ampliar a legibilidade do JSON mas, considerando os requisitos deste post, usaremos exemplos com o gron, já mencionado e linkado acima. Se você usar, numa shell compatível, o comando gron 'https://arram.senta-la.cloud/api/v1/directory?local=true' | head, verá uma resposta como a da tela abaixo:


Tela de terminal mostrando o comando gron especificado acima, e com resposta descrita logo abaixo, no texto do post.
Resposta da API decodificada com um campo por linha, usando o utilitário gron.

Note que agora, ao invés de uma linha gigante, temos uma sequência de linhas, cada uma mostrando um campo da resposta da API. Na tela acima, limitada a 10 linhas, todas as linhas se referem a um mesmo usuário e começam com json[0], como nos exemplos:


json[0].acct = "autobrain";
json[0].created_at = "2025-04-23T00:00:00.000Z";
json[0].discoverable = true;
json[0].display_name = "VISITE O DECORADO";

A listagem quanto ao primeiro usuário continua ainda por muitas outras linhas não visíveis na resposta (porque nós a cortamos com o filtro head) e, quando acabar, o próximo usuário começa em seguida, aí com o prefixo json[1], e assim por diante. Veja um exemplo das linhas da transição entre o 3º e o 4º usuários:


json[3].url = "https://arram.senta-la.cloud/@medidor";
json[3].username = "medidor";
json[4] = {};
json[4].acct = "TrendsBR";
json[4].bot = true;

Na versão atual da API, geralmente cada usuário é descrito por mais de 20 campos – ou seja, mais de 20 linhas geradas pelo gron –, e a último delas é referente ao campo username.

Um script básico para listar usuários

Este post de hoje nasceu de um script criado para apoiar a elaboração de uma lista dos usuários mais ativos da instância brasileira organica.social, que em dezembro de 2025 recebeu um grande influxo de usuários alcançados por uma bem-sucedida ação da Biana na rede Bluesky.

Tudo que precisamos para fazer uma lista dessas pode ser obtido pelo endpoint /api/v1/directory, que conhecemos acima. Como a organica.social recebeu centenas de novos usuários, será necessário buscar nela várias páginas de resposta (cada página listando 80 contas, como vimos acima).

Entre os diversos atributos da conta, que a resposta da API descreve, os que nos interessam especialmente são o login, o nome, a data de criação da conta (pois para a nossa listagem queremos só os recém-chegados) e a contagem de posts.

Adicionaremos também o campo referente à quantidade de contas que a pessoa segue, para assim ser mais fácil identificar uma parte das pessoas que criou conta apenas para "reservar o login". Os campos são os seguintes, retirados diretamente da resposta do gron sobre a minha conta pessoal:


json[0].display_name = "VISITE O DECORADO";
json[0].following_count = 1194;
json[0].indexable = true;
json[0].statuses_count = 9579;
json[0].username = "autobrain";

Há uma infinidade de maneiras de extrair esses dados usando linguagens e utilitários, e o programa a seguir, em shell e awk (com apoio do gron) é uma delas.

Ele baixa as 6 primeiras páginas de resultados, o que corresponde a até 480 contas - ou, no caso, as 480 contas locais da instância, que tiverem postado mais recentemente. Em seguida, ele faz um processamento básico de formatação das linhas (extraindo a identificação do campo e o ";" ao final da linha) e produz a saída em formato CSV básico, ou seja, com os campos separados por vírgulas, as linhas separadas por Return, e as strings entre aspas.

Segue a listagem completa:


#!/usr/bin/env bash
#
# masto_userdir.sh - exemplo de uso da API 'directory' do Mastodon
#
# Copyright (c) 2025,  Augusto Campos (https://augustocampos.net/).
# Licensed under the Apache License, Version 2.0.
#

echo "DISPLAY_NAME,FOLLOWING_COUNT,INDEXABLE,STATUSES_COUNT,USERNAME"
delta=0
inst="organica.social"
url="https://$inst/api/v1/directory?local=true&limit=80&offset=$delta"

# loop de 6 leituras
for i in {1..6}; do
  gron "$url" | awk '
  # filtra só as linhas desses campos mencionados:
  /\.(display_name|following_count|indexable|statuses_count|username) = / {

    # separador será uma vírgula, exceto no último campo ("username"), 
    # quando será um Return:
    SEP=","
    if ($1 ~ /\.username/) SEP="
"

    # remove a identificação do campo ("json[x].xxx"), o "=" e o ";"
    $1=$2=""
    sub(/;$/,"")

    # gera a saída com o conteúdo do campo e o separador
    printf("%s"SEP, substr($0,3))
  }'

  # antes de repetir o loop, aumenta o valor que será usado no "offset"
  delta=$((delta + 80))
done

Explicações sobre a linguagem estão fora do escopo deste post, mas incluí comentários nos trechos do código, para orientar quem preferir implementar com estrutura similar, em sua tecnologia favorita.

A saída produzida será algo similar a essa, que é o resultado de rodar o mesmo programa, mas direcionado à minha instância:


DISPLAY_NAME,FOLLOWING_COUNT,INDEXABLE,STATUSES_COUNT,USERNAME
"Locutora de feeds",1,true,1168,"locutora"
"VISITE O DECORADO",1194,true,9581,"autobrain"
"Gugu",0,true,176,"gugu"
"Medidor de ranço",11,true,63,"medidor"
"Trends Brasil 🚀",1,true,827,"TrendsBR"
"Madame Sandra Rosa Madalena",22,true,31,"MadameSandra"
"Tags Temáticas BR",2,true,366,"TagsBR"
"Bebê Taz Ininteligível",24,true,433,"aridiculaideia"
"Sextou, galera!",1,true,6,"sextou"

Note que esse formato de saída com linhas, aspas e separação por vírgulas, conhecido como CSV (ou RFC 4180), serve para ser importado em planilhas, bancos de dados e outros sistemas, e aí usá-los para reordenar, classificar, aplicar condições, gerar gráficos e o que mais você quiser fazer com essas informações – no meu caso, serviu para gerar uma thread no Mastodon.

O programa acima é bem cru, e tem muitas oportunidades de melhoria para você se exercitar, inclusive pela adição de tratamento de erro e de ser capaz de perceber que ele está tentando ler mais usuários do que a instância tem - na versão acima, o loop de 6 leituras é feito até mesmo para instâncias com menos de 80 usuários, e aí repete várias vezes a resposta da primeira leitura.

Um detalhe importante é que, embora o protocolo do Mastodon preveja que esse endpoint da API seja acessado sem precisar de autenticação, algumas instâncias configuram (com intenções variadas e eficácia baixa) bloqueios a esse acesso não autenticado, então se você experimentar em uma instância e não receber resposta, essa é uma das razões possíveis.

Referências e documentação

Venho preferindo exemplos com operações simples, envolvendo apenas um endpoint de API por vez, e com acesso a dados públicos, porque desenvolver interfaces ou clientes para o Mastodon tem uma escala de complexidade a ser vencida, como em tudo na vida, e não há razão para começar já tentando operações difíceis, autenticadas e envolvendo múltiplas instâncias e entidades.

Lembre-se que a API do Mastodon é bem documentada, e desde já recomendo os capítulos "Getting Started with the API", "Playing with public data" e "Directory API methods" para entendimento aprofundado do que apresentei acima com bem menos detalhes.

Leia também o post anterior: t Programando bots para o Mastodon: obtendo dados via API.

O valor da Duolingo e o mercado ~sedento por IA

Às vezes o mesmo fator que barateia custos também corrói valor, e essa é a história contada pelas ações do Duolingo em 2025.

A gente ouve o tempo todo que o mercado está sedento por IA. É verdade, dependendo do contexto, mas já não está mais como foi até recentemente, e esse gráfico das ações do Duolingo nos EUA ilustra isso muito bem.

Em abril o valor subiu muito, após anunciarem que iam demitir a galera dos idiomas e trocar por IA. Era verdade, e eles produziram bem mais conteúdo desse jeito. Mas também é verdade que isso produziu uma dose de má-vontade com a marca, por parte de uma parcela do mercado, especialmente quando surgiram relatos sobre a qualidade variável dos conteúdos produzidos a partir de então.


Gráfico do valor da ação do Duolingo na NASDAQ ao longo dos últimos 365 dias, descrito em maior detalhe no parágrafo imediatamente seguinte, no texto do post.
Gráfico do valor da ação do Duolingo na NASDAQ ao longo dos últimos 365 dias.

O gráfico acima mostra uma escalada súbita na virada de abril para maio (logo após o CEO anunciar que ia trocar por IA a mão de obra contratada que lidava com idiomas), e uma queda também súbita na virada de outubro para novembro, logo após a divulgação da previsão de desempenho para o próximo ano.

O mesmo gráfico também permite verificar que a ação vale hoje 38% a menos do que há 365 dias, e que entre a subidona e a descidona tivemos um longo período de queda menos acentuada – a realidade não sustentou as previsões do final de abril.

Por que o valor despencou em novembro?

Tem toda uma questão da dinâmica (e aprendizado) do mercado aí envolvida, mas também dá pra notar um ponto importante relacionado à estratégia: em abril/maio o que o mercado valorizou foi um aspecto de processos internos da empresa: ia ficar mais barato produzir o serviço que os clientes da empresa topam comprar.

Já em novembro, o que aconteceu é sobre algo mais relevante: a percepção (que veio quando a empresa publicou o relatório obrigatório sobre expectativas para o ano seguinte) de que aquele ganho de abril está ao alcance de todos os concorrentes, e o diferencial da proposta de valor do Duolingo já era.

Ou seja: oferecer grande volume de material sobre idiomas produzido por IA é mais barato para o Duolingo, mas está ao alcance de diversos outros concorrentes também, e o produto de todos tende a ficar parecido – e a percepção de liderança dessa marca foi abalada.

Pode mudar? Pode, mercado de ações é dinâmico. Inclusive, quem sabe alguém contrata aquelas pessoas que em abril ficaram disponíveis, e recupera o diferencial?

Cuidado com o imobilismo bem intencionado

Não pergunte se você é uma boa pessoa, e sim se você está fazendo coisas boas.

Quantas iniciativas coletivas eu já vi naufragarem porque, ao invés de prevalecer o impulso de quem queria produzir avanços com atos concretos, prevalecia a atitude paralisante de quem defende evitar a qualquer custo fazer qualquer coisa imperfeita.

Isso é um mal frequente em organizações voluntárias, e em grupos motivados por um ideal de evolução – mas pode se manifestar até na vida individual, quando a pessoa se pergunta muito mais “será que eu sou uma boa pessoa?” do que “será que o que estou fazendo é bom?”

E há uma pergunta ainda melhor pra impulsionar a mudar o mundo (e a própria realidade individual) para melhor: “Qual coisa boa eu posso fazer, aqui e agora?”


Foto de um ponbo que comeu o miolo de uma fatia de pão e acabou ficando com a casca dela pendurada em seu pescoço, como um colar.

Tentar ser uma boa pessoa, embora seja uma atitude virtuosa, pode ser sufocante e paralisante. É mais frutífero o caminho de quem procura, ao invés disso, fazer coisas boas (ou “fazer o bem”, dependendo do contexto).

Não se trata de defender ações perversas, desonestas ou irresponsáveis: elas são incompatíveis com o conceito de fazer coisas boas.

O critério da ação voluntária e orientada a ideais não pode ser aquele modelo inodoro e insípido do "zero defeito" ou da busca por zerar riscos - os processos precisam ser orientados a produzir o resultado, por meios bons.

Uma coisa de cada vez

Quando eu escrevo (ou crio em qualquer mídia), uma coisa que funciona muito bem pra mim é não misturar a hora da criação com a hora da edição.

Na hora da criação, tem que dar vazão às ideias. Na edição a gente seleciona, reordena, edita, rejeita frases, parágrafos e capítulos, reescreve, etc.

Tentar fazer as duas coisas ao mesmo tempo parece eficiente, mas sufoca a criação.

O guarda-roupa monocromático ajuda até nisso

Me faltam as habilidades de costura, mas na linha da atitude punk de combater a obsolescência programada, as minhas roupas começaram a ter uma sobrevida grande depois que eu passei a lembrar de tingi-las (deixo na lavanderia e eles fazem isso pra mim) quando elas começam a perder a cor original.

O meu guarda-roupa é bem monocromático e isso ajuda, claro.