Todos funcionam como slash command (/ping) e como comando de texto (!ping, com o
prefixo do .env). Os apelidos entre parênteses valem só no modo texto.
| Comando | O que faz | |
|---|---|---|
| 🔨 | /ban · banir |
Bane um usuário do servidor |
| 🕊️ | /unban · desbanir |
Remove o banimento |
| 👢 | /kick · expulsar |
Expulsa um usuário |
| 🔇 | /mute · mutar · silenciar · calar |
Silencia no texto e na voz |
| 🔉 | /unmute · desmutar · dessilenciar |
Devolve a voz |
| 🧹 | /clear <quantidade> · limpar |
Apaga as últimas mensagens do canal |
A faxina respeita o que não é dela: mensagem fixada fica, e o que passou de 14 dias o Discord não deixa apagar em lote — o embed de resposta diz quantas saíram e quantas ficaram, e por quê. O aviso some sozinho alguns segundos depois, pra não virar bagunça nova no canal recém-limpo.
| Comando | O que faz | |
|---|---|---|
/play · p · tocar |
Toca uma faixa ou põe na fila | |
| 📚 | /playlist add · pl add |
Acha uma playlist do YouTube pelo nome (ou link) e põe inteira na fila |
| ⏭️ | /skip · s · pular · next |
Pula a faixa atual |
| ⏸️ | /pause · pausar |
Pausa a reprodução |
| ⏯️ | /resume · voltar · continuar |
Retoma de onde parou |
| ⏹️ | /stop · parar |
Para tudo e limpa a fila |
| 👋 | /disconnect · leave · sair · dc |
Sai do canal de voz |
| 📜 | /queue · q · fila |
Mostra a fila |
| 💿 | /nowplaying · np · agora · tocando |
Faixa atual e progresso |
| 🔀 | /shuffle · embaralhar |
Embaralha a fila |
| 🔁 | /loop · repeat · repetir |
Repete a faixa, a fila, ou nada |
| ⏩ | /seek · ir · pular-para |
Anda para um ponto da faixa |
| 🔊 | /volume · vol · v |
Mostra ou muda o volume |
Restart no meio da música não perde nada: o bot guarda o que cada servidor estava tocando
(faixa, posição, fila, volume, loop) no volume aleph-data e, ao voltar, entra de novo no
canal e continua de onde parou — desde que ainda tenha alguém lá e faça menos de 30 minutos.
| Comando | O que faz | |
|---|---|---|
| 📺 | /ma <nome> · mal · myanimelist |
Ficha do anime no MyAnimeList |
| 📖 | /mg <nome> · manga · mangá |
Ficha do mangá, com a sinopse em português quando tem |
A ficha vem em embed com a capa, a nota do MAL e quantos votos ela tem, ranking, formato,
situação, período, estúdio (ou autor), gêneros e a sinopse inteira — traduzida pro português
na hora (via Google), com aviso quando o tradutor não respondeu e ela ficou em inglês. No modo
texto são !ma Naruto e !mg Berserk — quem escrever !ma mangá Berserk, como era antes, é
mandado pro !mg.
Sem configurar nada os dados vêm do AniList, que é aberto e não pede
chave — o link do embed continua indo pro MyAnimeList. Com um MAL_CLIENT_ID no .env quem
atende primeiro é a API oficial, e o AniList fica de
reserva pra quando ela cair — veja Configuração.
| Comando | O que faz | |
|---|---|---|
| 📖 | /help · ajuda · comandos |
Lista o que o bot sabe fazer |
| 📡 | /ping |
Latência do gateway |
Com uma API_KEY no .env o bot sobe junto uma API HTTP — no mesmo processo, lendo direto
do gateway, dos players e da lista de comandos. Só leitura: status e estatísticas. Sem a
chave ela não existe, e nenhuma porta abre.
Pra ligar:
- Gere uma chave com
openssl rand -hex 32e ponha noConfig/.env:API_KEY=<a chave>. - Suba o bot de novo —
docker compose up -d, oudotnet runfora do Docker. O boot avisaAPI ligadano log. - Confira com o
/api/v1/health, que não pede chave:curl http://localhost:8080/api/v1/healthdevolve{"status":"ok","discord":"connected","lavalink":"connected"}.
Daí em diante, o /api/v1/openapi.json descreve a API inteira, e a coleção do Postman
já traz as rotas montadas.
| Rota | O que devolve | |
|---|---|---|
| 💓 | GET /api/v1/health |
ok, degraded (responde comando, mas sem música) ou down (sem Discord, com 503).Não pede chave — é a que monitor de uptime chama (aceita HEAD também) |
| 🪪 | GET /api/v1/status |
Quem o bot é, versão, uptime, latência do gateway e o estado do Lavalink. Não pede chave |
| 📊 | GET /api/v1/stats |
Servidores, membros, pessoas online, players de música e memória |
| 📜 | GET /api/v1/commands |
Os comandos do /help, com a forma em barra, a de prefixo e os atalhos |
| 📖 | GET /api/v1/openapi.json |
O documento OpenAPI 3.1 da versão, montado das próprias rotas: os dois grupos (Status e Bot), o formato de cada resposta e um exemplo de verdade em cada uma.Não pede chave. É dele que sai a coleção do Postman, e a raiz do endereço ( /) leva pra ele |
O /api/v1/status, como o /api/v1/health, não pede chave:
curl http://localhost:8080/api/v1/status{
"bot": { "username": "AlephBot" },
"version": "1.0.0",
"runtime": ".NET 10.0.12",
"mode": "production",
"prefix": "!",
"startedAt": "2026-10-07T12:48:42.53+00:00",
"uptimeSeconds": 3600,
"discord": { "status": "connected", "latencyMs": 42 },
"lavalink": {
"status": "connected",
"since": "2026-10-07T12:48:50.11+00:00",
"stats": { "players": 1, "playingPlayers": 1, "uptimeSeconds": 3590, "memoryUsedMb": 180.2, "cpuLoad": 0.012, "reportedAt": "2026-10-07T13:48:20.4+00:00" }
}
}Os outros levam a chave no header X-Api-Key:
curl -H "X-Api-Key: $API_KEY" http://localhost:8080/api/v1/stats{
"guilds": 12,
"members": 3456,
"online": 210,
"commands": 21,
"music": { "players": 2, "playing": 1, "paused": 1, "queuedTracks": 17 },
"memory": { "workingSetMb": 142.7, "gcHeapMb": 38.1 }
}curl -H "X-Api-Key: $API_KEY" http://localhost:8080/api/v1/commands[
{
"name": "play",
"description": "Toca uma música ou põe ela na fila.",
"category": "music",
"slash": "/play <nome ou link>",
"text": "!play <nome ou link>",
"aliases": ["p", "tocar"]
}
]- Do bot só sai o nome: o ID dele não aparece em nenhuma rota, nem o avatar (o link do avatar carrega o ID dentro).
modeéproductionsempre queDOTNET_ENVIRONMENT=Production— o compose já põe —, mesmo com umDEV_GUILD_IDesquecido no.env.memberssoma os membros de cada servidor (quem está em dois conta duas vezes);onlinesão pessoas distintas, sem bots — o mesmo número da presença do bot.lavalink.statsé o último relatório do próprio Lavalink, que chega a cada minuto:nullaté o primeiro, e de novo depois de uma queda./api/v1/commandstraz um item por comando, em ordem alfabética (acima, só o/play). Otextjá vem com oPREFIXdo.env, eslashoutextvêmnullquando o comando só existe de um jeito.- Erro sai como
application/problem+json: 401 sem chave ou com a chave errada, 404, 405 e 429 pra quem passa do limite de pedidos. Chave errada fica anotada no log, com o IP de quem tentou — também atrás do Tailscale ou de um proxy na mesma máquina, que contam quem é peloX-Forwarded-For.
Important
Fora do Docker a API só escuta em localhost. No compose ela é publicada no 127.0.0.1 da
máquina — o HTTP leva a chave em texto puro, então pra abrir pra fora o certo é um proxy com
HTTPS na frente (Caddy, nginx). API_BIND=0.0.0.0 no .env da raiz abre direto, e o Docker
publica por cima do firewall (ufw): abre mesmo.
Tip
Pra ver de casa a API de um VPS não precisa abrir porta nenhuma: um túnel SSH traz ela até
você. ssh -N -L 8080:127.0.0.1:8080 usuário@vps — no PuTTY, Connection → SSH → Tunnels,
porta 8080 para 127.0.0.1:8080 — e o http://localhost:8080/api/v1/openapi.json abre no
seu navegador, que é o endereço que o Postman importa.
Note
Pra abrir de qualquer máquina sem instalar nada, o Tailscale Funnel publica a API com HTTPS, sem abrir porta no firewall:
sudo tailscale funnel --bg http://127.0.0.1:8080A raiz do endereço leva pro documento da API, e o resto responde nos caminhos dele. O que chega pelo Funnel vem marcado pelo Tailscale, e quem vem de fora passa por camadas:
healthestatussão abertos — é o que monitor de uptime e página de status chamam —, e o documento também: ele descreve a API, não entrega dado nenhum do bot;statsecommandspedem aAPI_KEYno headerX-Api-Key; sem ela, ou com ela errada, 401, e a tentativa fica no log com o IP de quem tentou;- 60 pedidos por minuto por IP (limite de pedidos) — passou disso, 429 até o minuto virar;
- nada daqui pode ser embutido em outro site (
X-Frame-Options: DENY).
De dentro (a sua rede, o Tailscale, o túnel SSH) nada disso pesa — fica só o limite de pedidos, dez vezes maior.
A pasta Postman/ tem a coleção e o environment desta API, prontos pro Import do Postman:
| Arquivo | O que é |
|---|---|
AlephBot.postman_collection.json |
As cinco rotas em dois grupos (Status e Bot), cada uma com um teste do que a resposta tem que trazer, mais uma chamada de propósito sem chave, que espera 401 |
AlephBot.postman_environment.json |
baseUrl, o endereço, e apiKey, a sua API_KEY. Ela fica vazia aqui, e um teste da suíte existe só pra ela nunca sair cheia |
Importe os dois, escolha o environment AlephBot e preencha a apiKey. O baseUrl já vem
em http://127.0.0.1:8080, que é o que o túnel SSH entrega; pra falar com a VPS pelo Funnel,
troque pelo endereço .ts.net. Se preferir montar a coleção você mesmo, o Postman importa o
/api/v1/openapi.json direto.
A coleção não envelhece sozinha: um teste da suíte confere, a cada dotnet test, que toda rota
do documento tem pedido nela e que ela não inventa rota que a API não tem.
Na VPS, o Newman — o Postman de linha de comando —
roda a mesma coleção contra a API no ar. Ele mora no compose atrás de um profile, então não
sobe junto no up:
docker compose --profile testes run --rm newmanEle chama o bot pelo nome na rede do compose e usa a ALEPH_API_KEY do .env da raiz, a mesma
do n8n. Teste vermelho sai com código diferente de zero, então dá pra encadear no deploy:
docker compose up -d && docker compose --profile testes run --rm newmanA versão mora no caminho: /api/v1/.... Toda resposta da v1 conta no header
api-supported-versions quais versões existem, e versão que não existe (/api/v2/...) é só um
endereço que não existe — 404. Mudança que quebra quem já usa nasce como /api/v2, do lado
da v1, que continua de pé.
O documento mora dentro da versão, ao lado das rotas dela, em /api/v1/openapi.json: assim um
mount só no Funnel, o /api/v1, leva a versão inteira.
Os endereços de antes da versão não quebram: o /api/health segue respondendo onde estava (é
ele que monitor de uptime chama), /api/status, /api/stats e /api/commands mandam pra v1
com 308 — mesmo método, endereço novo — e o /api/openapi.json manda pro documento da v1.
Cada IP tem uma cota de pedidos por minuto — no IPv6, conta o /64 inteiro, que é o que o
provedor entrega pra uma casa só:
| Quem pede | Pedidos por minuto |
|---|---|
| De fora (Funnel ou IP público) | 60 |
| De dentro (a sua rede, o Tailscale, o túnel SSH) | 600 |
Toda resposta conta a cota no X-RateLimit-Limit e quanto ainda sobra no
X-RateLimit-Remaining:
$ curl -si http://localhost:8080/api/v1/health | grep -i ratelimit
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 599Passou da cota, 429 com Retry-After dizendo em quantos segundos voltar — a cota inteira
volta quando o minuto vira. Conta todo pedido, o documento inclusive, e chave errada também
gasta. A cota de dentro é maior só pra segurar script em loop: a
API mora no mesmo processo que atende o Discord. No log, quem passa da cota aparece uma vez por
minuto, e não a cada pedido recusado.
Um n8n sobe junto no compose e usa a API sozinho. Cada fluxo é um arquivo
em n8n/fluxos:
| Fluxo | O que faz | |
|---|---|---|
| 💓 | Saúde | Pergunta o /api/v1/health a cada 5 minutos e avisa no Discord quando o bot cai e quando volta — uma vez por mudança, sem repetir |
| 📈 | Histórico de stats | Guarda o /api/v1/stats a cada 15 minutos numa tabela do próprio n8n, a aleph_stats |
| 📜 | Comandos | De hora em hora lê o /api/v1/commands e, quando a lista muda, regrava o comandos.md |
| 📊 | Painel | O estado de agora (pelo /api/v1/status e pelo /api/v1/stats) e as últimas 24 h ou 7 dias do histórico, em http://localhost:5678/webhook/aleph-painel |
| 💾 | Backup diário | Às 3h guarda o status e os stats do dia num JSON |
| Erros dos fluxos | Quando um dos outros falha — chave errada, API fora —, avisa no mesmo canal |
O comandos.md e os backups (backups/aleph-AAAA-MM-DD.json) ficam em n8n/arquivos/, na
pasta do bot na VPS.
Pra ligar, na VPS:
-
Confira a folga com
free -m: o n8n usa uns 450 MB de memória (o compose limita em 1 GB). -
Crie um webhook no canal que vai receber os avisos (Editar canal → Integrações → Webhooks → Novo webhook → Copiar URL do webhook) e ponha no
.envda raiz, junto com a mesmaAPI_KEYdoConfig/.env:echo "ALEPH_API_KEY=$(grep '^API_KEY=' Config/.env | cut -d= -f2-)" >> .env echo 'ALEPH_DISCORD_WEBHOOK=https://discord.com/api/webhooks/...' >> .env
-
git pulledocker compose up -d— o n8n sobe só nolocalhostda VPS. -
No PuTTY, um túnel a mais (Connection → SSH → Tunnels): porta
5678para127.0.0.1:5678. Abra http://localhost:5678 e crie a conta de dono. -
sh n8n/instalar.shimporta e publica os fluxos.
Depois de um git pull que mude algum fluxo, rode o sh n8n/instalar.sh de novo — o fluxo
volta a ser o do repo, e o que você tinha mudado nele pelo editor do n8n se perde.
Note
O n8n mora na mesma VPS do bot: ele avisa quando o bot, o Discord ou o Lavalink caem, mas não quando a VPS inteira cai — aí não sobra ninguém pra avisar.
Toda a configuração vem de variáveis de ambiente. Em desenvolvimento elas são lidas de
Config/.env; em produção, do próprio ambiente do contêiner.
cp Config/.env.example Config/.envDepois preencha o TOKEN:
| Variável | Padrão | Descrição | |
|---|---|---|---|
TOKEN |
✅ | — | Token do bot no Discord Developer Portal |
PREFIX |
! |
Prefixo dos comandos de texto | |
DEV_GUILD_ID |
vazio | ID do servidor de testes: marca o bot como em desenvolvimento (banner, log e /api/v1/status).Não muda onde os slash commands são registrados — eles são sempre globais. Em produção ( DOTNET_ENVIRONMENT=Production, como no compose) não vale |
|
LOG_LEVEL |
Information |
Trace · Debug · Information · Warning · Error · Critical |
|
LAVALINK_URI |
http://localhost:2333/ |
Endereço REST do servidor Lavalink | |
LAVALINK_PASSWORD |
youshallnotpass |
Senha do Lavalink | |
MUSIC_IDLE_MINUTES |
2 |
Minutos parado (canal vazio ou nada tocando) antes de sair da voz | |
YOUTUBE_LOGIN |
false |
Pede o login do YouTube no console quando não há token guardado. Plano B pra servidor: com o yt-audio do compose o YouTube toca sem conta — veja YouTube num servidor |
|
YOUTUBE_AUDIO_URI |
vazio | Endereço do yt-audio, o serviço que toca o YouTube sem conta. O compose preenche (http://yt-audio:8080/).Vazio = o YouTube toca só pelo plugin do Lavalink — veja YouTube num servidor |
|
MAL_CLIENT_ID |
vazio | Client ID da API oficial do MyAnimeList para o /ma e o /mg.Vazio = usa só o AniList, sem chave. Com a chave, o MAL atende primeiro e o AniList vira reserva. Para criar um: Create ID, App Type other, e copie o Client ID |
|
API_KEY |
vazio | Chave da API. Vazio = API desligada, nenhuma porta aberta. Mínimo de 16 caracteres — openssl rand -hex 32 gera uma boa |
|
API_PORT |
8080 |
Porta da API. Fora do Docker ela só escuta em localhost; no compose quem manda é o .env da raiz |
✅ = obrigatória
Important
Config/.env está no .gitignore e no .dockerignore — ele nunca entra no repositório
nem na imagem. Se o bot subir sem TOKEN, ele cria o arquivo a partir do template e para
com a instrução na tela.
No Developer Portal, aba Bot, ligue:
- Message Content Intent — sem ela os comandos de texto não chegam
- Server Members Intent
- Presence Intent — usada para contar quem está online
Sobe sete contêineres na rede interna do compose: o bot, o Lavalink (áudio), o
yt-cipher, que decifra as assinaturas do player do YouTube — o decifrador embutido no
plugin quebra a cada mudança do YouTube; esse acompanha —, os três que tocam o YouTube sem
conta (yt-audio, pot e warp, veja YouTube num servidor) e o
n8n, que roda a automação com a API.
docker compose up -d --build
docker compose logs -f alephbotO bot só é iniciado depois que o Lavalink responde ao healthcheck, então o primeiro up
pode levar um minuto: o plugin do YouTube é baixado antes do servidor abrir a porta.
O compose.yaml injeta LAVALINK_URI, LAVALINK_PASSWORD e YOUTUBE_AUDIO_URI no bot,
então o que estiver no Config/.env para essas três chaves é ignorado. O que o compose lê
é o .env da raiz do projeto — um arquivo diferente do Config/.env:
| Variável | Padrão | Descrição |
|---|---|---|
LAVALINK_PASSWORD |
youshallnotpass |
Senha do Lavalink, nos dois lados |
YOUTUBE_REFRESH_TOKEN |
vazio | Semente do login do YouTube, o plano B — veja abaixo |
YOUTUBE_PO_TOKENYOUTUBE_VISITOR_DATA |
vazio | Par que responde ao "Sign in to confirm you're not a bot" nos clients WEB. Gere os dois com docker run --rm quay.io/invidious/youtube-trusted-session-generator |
API_PORT |
8080 |
Porta da API na máquina. Dentro do contêiner ela é sempre 8080 |
API_BIND |
127.0.0.1 |
Onde essa porta é publicada. 0.0.0.0 abre pra rede — leia o aviso da API antes |
ALEPH_API_KEY |
vazio | A mesma API_KEY do Config/.env, pro n8n chamar stats e commands |
ALEPH_DISCORD_WEBHOOK |
vazio | Webhook do canal que recebe os avisos do n8n |
Quatro volumes sobrevivem ao docker compose down: aleph-logs (logs em arquivo),
aleph-data (o token do YouTube e o que cada servidor estava tocando), lavalink-plugins
(o plugin baixado) e n8n-data (a conta do n8n, os fluxos e o histórico dos stats).
down -v apaga os quatro — se você usa o login do YouTube, ele tem que ser refeito.
Note
A porta 2333 do Lavalink não é publicada: ele só existe dentro da rede do compose. A da
API é, mas só no 127.0.0.1 da máquina — e só responde com API_KEY no Config/.env.
A 5678 do n8n também, só no 127.0.0.1: chega até você pelo túnel SSH.
De um IP de datacenter (EC2, DigitalOcean e afins) o YouTube não entrega áudio a quem não está logado: a busca funciona, o áudio responde "This video requires login" ou "Sign in to confirm you're not a bot". O compose contorna isso sem conta nenhuma, com três contêineres:
yt-audio(a pastaYtAudio) pega o áudio com o yt-dlp, que acompanha as mudanças do YouTube toda semana, e serve por HTTP na rede interna;potgera o token anti-robô (PO token) que o YouTube passou a exigir, com o bgutil;warpé a saída pra internet, pelo Cloudflare WARP: do IP da AWS o YouTube recusa mesmo com o token; do da Cloudflare, entrega.
O bot continua buscando pelo plugin do Lavalink — título, capa e fila vêm de lá —, mas na
hora de tocar uma faixa do YouTube manda o Lavalink tocar http://yt-audio:8080/v/<id>. Se
o yt-audio falhar, aquela faixa vai pelo plugin, como antes. Quem liga isso é o
YOUTUBE_AUDIO_URI, que o compose já preenche; transmissão ao vivo fica sempre com o plugin.
Pra acompanhar — cada música mostra o formato e quanto o yt-dlp levou, e cada recusa, o motivo:
docker compose logs -f yt-audioPra testar um vídeo direto (204 = o áudio veio; 502 vem com o motivo):
docker compose exec lavalink curl -s -w "%{http_code}\n" http://yt-audio:8080/preparar/dQw4w9WgXcQPra conferir se o túnel está de pé (warp=on):
docker compose exec lavalink curl -x socks5h://warp:9091 -fsS https://www.cloudflare.com/cdn-cgi/trace | grep warpQuando o YouTube mudar e o yt-audio começar a recusar, o conserto quase sempre é um yt-dlp
mais novo: troque a versão no YtAudio/requirements.txt (a lista está no
PyPI) e reconstrua só ele:
docker compose up -d --build yt-audioNote
O WARP gratuito não foi feito pra isso, e o YouTube pode passar a recusar os IPs da
Cloudflare. Se o áudio voltar a falhar, ligue o login abaixo como reserva: a faixa que o
yt-audio não conseguir toca pelo plugin, com a conta.
Quem usa o token é o Lavalink, mas quem pede o login é o bot — assim o código aparece no console dele, junto do resto.
Ligue YOUTUBE_LOGIN=true no Config/.env, suba e acompanhe:
docker compose up -d --build
docker compose logs -f alephbotEla pede o código na saudação dela:
Denia · login do YouTube
O YouTube não acredita que um servidor escuta música. Faz o login que eu espero.
1. abre https://www.google.com/device
2. entra com uma conta descartável (não a sua principal)
3. digita o código ABC-DEF-GHI
Depois que você autoriza, acabou: o bot guarda o refresh token no volume aleph-data
(/app/data/youtube-token.json) e entrega ele ao Lavalink toda vez que os dois se
conectam — boot, queda de rede ou restart só do áudio. Nada de colar em arquivo nem recriar
container.
O token não tem prazo, mas o Google invalida quando cisma com a conta. Quando isso acontece o Lavalink recusa a entrega e o bot pede um login novo sozinho, no mesmo log — digite o código e a música volta, sem restart. Se o Google trocar o token por um novo durante uma renovação, o bot guarda o novo ao desligar.
Já tem um token de antes? Ponha em YOUTUBE_REFRESH_TOKEN no .env da raiz (o que o
compose lê): ele é a semente do primeiro boot, e também vale quando você cola um token
diferente ali (trocar de conta, por exemplo). Fora isso a variável pode ficar vazia.
Note
Use uma conta Google descartável: o padrão de acesso de um bot pode fazer o YouTube
sinalizar a conta — e é isso, não o tempo, que mata o token. Pra voltar a ficar sem conta,
só com o yt-audio, desligue o YOUTUBE_LOGIN, esvazie o YOUTUBE_REFRESH_TOKEN e apague
o token guardado (docker compose exec alephbot rm /app/data/youtube-token.json): com um
token guardado, o bot pede login novo quando o Google recusa, mesmo com o YOUTUBE_LOGIN
desligado.
Precisa de um Lavalink de pé. O Lavalink/application.yml do repositório já está
configurado com os padrões que o bot espera, com uma ressalva: ele aponta o decifrador de
assinaturas para http://yt-cipher:8001, um nome que só existe na rede do compose. Fora
dele, suba o serviço à parte e troque o remoteCipher.url para http://localhost:8001:
# o decifrador, em um terminal
docker run --rm -p 8001:8001 -e OVERRIDE_PLAYER_VARIANT=IAS ghcr.io/kikkia/yt-cipher:master
# o Lavalink em outro, com o application.yml deste repositório ao lado do jar
java -jar Lavalink.jar
# e o bot num terceiro
dotnet runFora do compose o YOUTUBE_AUDIO_URI fica vazio, e o YouTube toca só pelo plugin.
Testes de unidade em xUnit, no tests/AlephBot.Tests. Rodam sem Discord,
sem Lavalink e sem rede:
dotnet test --project tests/AlephBot.TestsPra ver o resultado na paleta do console do bot — agrupado por pasta e classe e, quando algo
quebra, o teste, a mensagem do Assert e a linha:
dotnet run --file tests/relatorio.cs
dotnet run --file tests/relatorio.cs -- --filter-class AlephBot.Tests.Commands.MuteCommandTestsImportant
É --file mesmo: na raiz mora o AlephBot.csproj, e sem a opção o dotnet run sobe o bot em
vez do relatório.
No VPS, sem .NET instalado, o SDK vem do Docker — e o -t é o que traz as cores:
docker run --rm -t --user "$(id -u):$(id -g)" -e HOME=/tmp -v "$PWD":/src -w /src \
mcr.microsoft.com/dotnet/sdk:10.0 dotnet run --file tests/relatorio.csCobrem o que quebra calado: o .env e a config, a duração do /mute e a posição do /seek, o
link de rádio do YouTube, o áudio do YouTube pelo yt-audio (e a volta pro plugin quando ele
falha), a leitura do MyAnimeList, a auditoria dos comandos de verdade (gatilho faltando, nome
ou atalho repetido) e a API — que sobe inteira num servidor em memória pra conferir quem passa
sem chave, o documento OpenAPI e a coleção do Postman, conferida contra ele.
Os do yt-audio, em Python, rodam no build da imagem dele, com o yt-dlp e o YouTube de
mentira: o pedaço que o Lavalink pede no /seek, o link renovado e as recusas. Se algum
quebrar, docker compose build yt-audio falha e a imagem nem sai.
Note
O global.json da raiz põe o dotnet test na Microsoft.Testing.Platform, que é como o
.NET 10 roda o xUnit v3. Sem ele o comando acima recusa o projeto.
Config/ Carregamento do .env e o objeto de configuração
Core/
Commands/ Um arquivo por comando, agrupados por categoria
Personality/ Todo texto que o usuário lê — mudar o tom do bot é mexer só aqui
Threnodian/ Bootstrap: host, DI, logging, gateway e os serviços de fundo
Inicialização/ O Main e a montagem do host: quem acende tudo
Api/ A API HTTP: rotas, a chave, o documento OpenAPI e o que ela lê do bot
Youtube/ Login (o token guardado e entregue ao Lavalink), a busca de playlist e o
áudio pelo yt-audio
Players/ A foto de cada player e a volta depois de um restart
Lavalink/ application.yml do servidor de áudio
YtAudio/ O yt-audio: o áudio do YouTube sem conta, pelo yt-dlp (Python)
n8n/ A automação com a API: os fluxos, o script que instala e a pasta onde eles gravam
Postman/ A coleção e o environment da API, pro Postman e pro Newman do compose
tests/ Testes de unidade (xUnit): a config, os parsers, os comandos e a API em memória
Tip
Comandos são descobertos por reflection: qualquer classe que implemente ICommand entra
no /help sozinha, sem registro manual em lugar nenhum.
| .NET 10 SDK | obrigatório |
| Docker | opcional — para o compose |
| Java 17+ | só para rodar o Lavalink na mão |