Bot para o Fediverso que traduz posts automaticamente quando mencionado.
Mencione @[email protected] em qualquer post e ele responde com o conteúdo traduzido para o idioma configurado.
@[email protected]
Bonjour tout le monde, comment ça va ?
@[email protected]
🌐 [FR → PT] Olá a todos, como vão vocês?
Testado e funcionando com Mastodon e instâncias compatíveis com ActivityPub.
| apkit | Toolkit ActivityPub para Python — cuida de HTTP Signatures, WebFinger e NodeInfo |
| FastAPI | Servidor web assíncrono (vem como dependência do apkit) |
| LibreTranslate | Detecção automática de idioma e tradução — open source, self-hostável |
| Dynaconf | Configuração por ambiente com suporte a secrets |
| SQLAlchemy + SQLite | Persistência leve de followers, sem dependências externas |
| uv | Gerenciamento de dependências e ambiente virtual |
- Python 3.12+
- uv instalado
- make instalado (disponível na maioria dos sistemas Unix)
- Acesso a uma instância LibreTranslate (pública ou self-hosted)
- Um domínio com HTTPS apontando para o servidor (obrigatório para ActivityPub)
curl -LsSf https://astral.sh/uv/install.sh | shgit clone https://github.com/Riverfount/translate-bot
cd translate-botmake install-devO uv cria automaticamente o ambiente virtual em .venv e instala tudo a partir do uv.lock. Não é necessário ativar o venv manualmente.
make gen-keysIsso cria keys/private.pem e keys/public.pem. A chave privada é usada para assinar as atividades enviadas — nunca a versione no git.
Edite o settings.toml com o domínio do seu bot:
[production]
domain = "bot.seu-dominio.com"Crie o arquivo .secrets.toml com a API key da instância LibreTranslate, se necessário (já está no .gitignore):
[default]
libretranslate_api_key = "sua-chave-aqui"Instâncias self-hosted sem autenticação podem deixar a chave em branco. A instância padrão
https://libretranslate.comexige chave.
Para definir o ambiente ativo, crie um .env na raiz:
ENV_FOR_DYNACONF=production
make runPara desenvolvimento com hot-reload:
make devTodas as configurações ficam em settings.toml. Os segredos ficam separados em .secrets.toml.
# settings.toml
[default]
bot_username = "translatebot" # usuário do bot no Fediverso
bot_display_name = "Translate Bot 🌐"
bot_summary = "Mencione-me para traduzir qualquer post!"
target_language = "pt" # idioma padrão de destino
libretranslate_url = "https://libretranslate.com" # instância LibreTranslate
libretranslate_api_key = "" # deixe em branco se não exigir chave
database_url = "sqlite+aiosqlite:///./bot.db"
private_key_path = "keys/private.pem"
public_key_path = "keys/public.pem"
[development]
domain = "localhost"
database_url = "sqlite+aiosqlite:///./bot_dev.db"
[production]
domain = "bot.seu-dominio.com" # ← altere aquiQualquer configuração pode ser sobrescrita via variável de ambiente com o prefixo TRANSLATEBOT_:
TRANSLATEBOT_TARGET_LANGUAGE=en uv run uvicorn app.main:api --host 0.0.0.0 --port 8000Mastodon / Misskey / etc. Translate Bot
│ │
│ POST /users/translatebot/inbox │
│ {type: "Create", object: Note} ─────▶│
│ │
│ verifica HTTP Signature (apkit)
│ enfileira na fila assíncrona
│ retorna 202 Accepted imediatamente
│ │
│ [worker em background]
│ extrai texto do post
│ detecta idioma de origem
│ traduz via LibreTranslate
│ monta Note de resposta
│ assina com draft-cavage e envia
│ │
│ ◀── resposta traduzida na thread ───│
O handler do inbox retorna 202 imediatamente — servidores Mastodon têm timeout curto. A tradução acontece em um worker asyncio em background.
translate-bot/
├── app/
│ ├── main.py # Servidor ActivityPub + endpoints FastAPI
│ ├── config.py # Configurações via Dynaconf
│ ├── database.py # Engine e sessão SQLAlchemy async
│ ├── activitypub/
│ │ ├── actor.py # Perfil ActivityPub do bot
│ │ ├── keys.py # Carregamento das chaves RSA
│ │ └── handlers.py # Handlers de Follow e Create
│ ├── models/
│ │ └── follower.py # ORM model de followers
│ └── services/
│ ├── translate.py # Integração LibreTranslate
│ └── queue.py # Fila assíncrona asyncio
├── workers/
│ └── inbox_worker.py # Worker de tradução em background
├── scripts/
│ └── generate_keys.py # Geração de chaves RSA
├── tests/ # Suite de testes (pytest + anyio)
├── keys/ # Chaves RSA — git-ignored
├── settings.toml # Configurações (versionado)
├── .secrets.toml # Segredos — git-ignored
├── .env.example # Exemplo de variáveis de ambiente
├── Dockerfile # Imagem para deploy
├── Makefile # Comandos de gerenciamento do projeto
├── pyproject.toml # Dependências e metadados
└── uv.lock # Lockfile — deve ser versionado
# Rodar todos os testes (cobertura incluída automaticamente)
make test
# Modo verbose
make test-v
# Apenas um módulo
make test-file FILE=tests/test_handlers.py
# Sem relatório de cobertura (mais rápido)
make test-fastA cobertura é configurada automaticamente via pyproject.toml (branch coverage). A suite cobre translate, inbox_worker, handlers, actor/keys e os endpoints principais do servidor.
# Listar todos os comandos disponíveis
make help
# Verificar o código com o linter
make lint
# Corrigir erros de lint automaticamente
make lint-fix
# Formatar o código
make format
# Verificar tipos com mypy
make typecheck
# Formatar + lint + typecheck de uma vez
make check
# Remover artefatos gerados (cache, cobertura, build)
make clean
# Adicionar uma dependência
uv add nome-do-pacote
# Adicionar dependência só de desenvolvimento
uv add --group dev nome-do-pacote
# Atualizar apenas o apkit
uv lock --upgrade-package apkitmake docker-build
make docker-runPara acompanhar os logs em tempo real:
make docker-logsPara parar e remover o container:
make docker-stopAs variáveis IMAGE_NAME, IMAGE_TAG e PORT podem ser sobrescritas:
make docker-build IMAGE_NAME=meu-bot IMAGE_TAG=v1.0
make docker-run PORT=9000O protocolo ActivityPub exige HTTPS. Servidores Mastodon rejeitam conexões sem TLS.
A forma mais simples com Caddy:
# Caddyfile
bot.seu-dominio.com {
reverse_proxy localhost:8000
}
caddy runO Caddy obtém e renova o certificado Let's Encrypt automaticamente.
Para testar sem um servidor público, use o ngrok para expor o servidor local:
# Terminal 1 — túnel ngrok
ngrok http 8000
# Terminal 2 — servidor
make devAtualize o settings.toml com a URL do ngrok na seção [development] e defina ENV_FOR_DYNACONF=development no .env.
apkit ainda não é estável. A versão está fixada no
pyproject.toml. Antes de atualizar, leia o CHANGELOG do projeto.
LibreTranslate é open source e self-hostável. Para maior controle e sem custos por caractere, considere rodar sua própria instância. Instruções em libretranslate.com. O limite de 500 caracteres por requisição é configurável no código.
uv.lockdeve ser versionado no git. Ele garante que produção use exatamente as mesmas versões que desenvolvimento.
Vicente Marçal — @[email protected]
MIT