Skip to content
lucaass18Public

About

Bot de Discord em C# / .NET 10 com moderação e musica, feito com NetCord e Lavalink.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

AlephBot

Bot de Discord em C# com moderação, música e uma API de status.


C# .NET 10 Discord Docker

AlephBot v1.0.0 NetCord Lavalink License


Comandos

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.


Moderação

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.


Música

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.


Anime

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.


Geral

Comando O que faz
📖 /help · ajuda · comandos Lista o que o bot sabe fazer
📡 /ping Latência do gateway


API

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:

  1. Gere uma chave com openssl rand -hex 32 e ponha no Config/.env: API_KEY=<a chave>.
  2. Suba o bot de novo — docker compose up -d, ou dotnet run fora do Docker. O boot avisa API ligada no log.
  3. Confira com o /api/v1/health, que não pede chave: curl http://localhost:8080/api/v1/health devolve {"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 é production sempre que DOTNET_ENVIRONMENT=Production — o compose já põe —, mesmo com um DEV_GUILD_ID esquecido no .env.
  • members soma os membros de cada servidor (quem está em dois conta duas vezes); online sã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: null até o primeiro, e de novo depois de uma queda.
  • /api/v1/commands traz um item por comando, em ordem alfabética (acima, só o /play). O text já vem com o PREFIX do .env, e slash ou text vêm null quando 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 é pelo X-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:8080

A 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:

  • health e status sã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;
  • stats e commands pedem a API_KEY no header X-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.

Postman

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.

Teste de fumaça depois do deploy

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 newman

Ele 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 newman

Versões

A 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.

Limite de pedidos

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: 599

Passou 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.



Automação com a API (n8n)

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:

  1. Confira a folga com free -m: o n8n usa uns 450 MB de memória (o compose limita em 1 GB).

  2. Crie um webhook no canal que vai receber os avisos (Editar canal → Integrações → Webhooks → Novo webhook → Copiar URL do webhook) e ponha no .env da raiz, junto com a mesma API_KEY do Config/.env:

    echo "ALEPH_API_KEY=$(grep '^API_KEY=' Config/.env | cut -d= -f2-)" >> .env
    echo 'ALEPH_DISCORD_WEBHOOK=https://discord.com/api/webhooks/...' >> .env
  3. git pull e docker compose up -d — o n8n sobe só no localhost da VPS.

  4. No PuTTY, um túnel a mais (Connection → SSH → Tunnels): porta 5678 para 127.0.0.1:5678. Abra http://localhost:5678 e crie a conta de dono.

  5. sh n8n/instalar.sh importa 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.



Configuração

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/.env

Depois 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.


Intents privilegiadas

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


Rodando

🐳 Docker recomendado

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 alephbot

O 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_TOKEN
YOUTUBE_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.


YouTube num servidor

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 pasta YtAudio) pega o áudio com o yt-dlp, que acompanha as mudanças do YouTube toda semana, e serve por HTTP na rede interna;
  • pot gera 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-audio

Pra 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/dQw4w9WgXcQ

Pra 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 warp

Quando 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-audio

Note

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.

Login do YouTube plano B

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 alephbot

Ela 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.


💻 Local

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 run

Fora do compose o YOUTUBE_AUDIO_URI fica vazio, e o YouTube toca só pelo plugin.



Testes

Testes de unidade em xUnit, no tests/AlephBot.Tests. Rodam sem Discord, sem Lavalink e sem rede:

dotnet test --project tests/AlephBot.Tests

Pra 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.MuteCommandTests

Important

É --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.cs

Cobrem 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.



Estrutura

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.


Requisitos

.NET 10 SDK obrigatório
Docker opcional — para o compose
Java 17+ só para rodar o Lavalink na mão


Distribuído sob a licença MIT.

About

Bot de Discord em C# / .NET 10 com moderação e musica, feito com NetCord e Lavalink.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages