Bot de Discord para gerir uma guilda do WynnCraft. Veja o design completo em design.md.
Apenas duas, de propósito:
discord.js— gateway, REST e slash commands do Discordmongodb— driver oficial (sem ORM)
HTTP (fetch), agendamento e leitura de .env usam recursos nativos do Node (>= 20).
Todos os módulos do roadmap. Comandos:
| Comando | Função |
|---|---|
/link <nick> |
Vincula conta e dá o cargo de comunidade |
/unlink |
(Staff) Remove vínculo |
/config channel|role|param|show |
Configura canais, cargos e parâmetros |
/apply submit|status |
Candidatura + votação (24h, cooldown de reaplicação) |
/season start|end|current|list |
(Staff) Gerencia temporadas |
/leaderboard season|alltime |
Placar de guerras pela guilda |
/profile [nick] |
Progresso acumulado de um membro |
/war [nota] |
(WAR/MAIN WAR) Convocação de guerra com presença |
/tome join|leave|queue|grant |
Fila de Tomes (prioridade por pontos; 1 Tome por missão semanal, e só vale com 7 dias de guilda) |
/loan new|list|repay|cancel |
(Staff) Livro-razão de empréstimos |
/points show|leaderboard|add |
Sistema de pontos unificado |
/calc |
Conversor de esmeraldas (stx/le/eb/em) |
/uniforme |
Baixa o uniforme e a capa oficiais da Wynn Brasil |
/modpack |
Devolve o link de download do modpack oficial (mods.rar) |
/booth registrar|status|cancelar |
Lembrete de reset do booth (24h): avisa o dono ~5 min antes, no canal booth, com botão de parar |
/evento criar|ranking|listar|encerrar|cancelar|apurar |
Competição por período: quem mais fizer guild raids, guerras ou XP leva a recompensa |
/giveaway criar|encerrar|reroll|listar |
Sorteio com inscrição por botão |
Automático (jobs):
- Sync de cargos: cargo "Membro da Guilda" + "Top Contribuidor" (ranks são manuais) + reconciliação de ingresso/saída
- Monitoramento em tempo real (poller ~60s): painel ao vivo (
panel), logs de atividade (activity), território + recursos (territory) e auto-ping de guerra - Expiração de candidaturas (fecha e apura no prazo)
- Snapshot diário: progresso, placar de guerras e pontos (all-time + por season)
- Lembretes de empréstimo (a vencer / atrasados). As cobranças somem do canal 48h depois de o empréstimo fechar — o tópico com o acordo fica, é o registro
- Check-in de inatividade: quem estoura a margem recebe uma DM com dois botões antes de qualquer expulsão (ver abaixo)
- Eventos e sorteios: painel do evento se atualiza sozinho, o evento fecha e anuncia o pódio no prazo, e o sorteio é apurado no minuto do vencimento
- Auditoria (
logs) e erros do bot (errors)
Ninguém é expulso sem ser perguntado. Quando um membro estoura a própria
margem (inactivityDays + o perdão que os pontos de contribuição compram), o bot
manda uma DM com dois botões:
| Botão | O que acontece |
|---|---|
| 🎮 Ainda quero jogar | Ganha inactivityReturnDays (3) para entrar no jogo. Um login zera o contador e encerra o assunto |
| 👋 Perdi o interesse | O slot é liberado; o nick vai para a lista de kick na hora |
| (sem resposta) | Passadas inactivityCheckHours (24), o silêncio conta como desinteresse |
Quantas mensagens? No máximo duas por episódio de inatividade:
- o check-in acima, quando a margem estoura;
- só para quem clicou em ainda quero jogar e deixou os 3 dias passarem sem logar — avisando que o nick caiu na lista, que a expulsão não é automática e que um login ainda tira o nome de lá.
Quem simplesmente não responde não recebe a segunda: silêncio já é resposta, e insistir é exatamente o que a primeira mensagem promete não fazer.
A mensagem reforça o motivo da regra: o kick por inatividade existe só para liberar slot para quem está jogando. Não é banimento, não fica no histórico, e o membro pode voltar quando quiser refazendo o processo de entrada.
O resultado sai no /verificar (e no relatório diário do canal logs), num
bloco pronto para copiar e colar no jogo:
/gu kick Fulano
/gu kick Ciclano
Quem ainda tem prazo correndo aparece numa lista separada, com o que se espera de cada um (clicar ou logar) e até quando — para a staff não expulsar antes da hora.
Nunca entram na lista: quem voltou a jogar, quem a contribuição ainda protege, e quem sequer foi perguntado. Quem não tem vínculo no Discord (ou está com a DM fechada) entra direto, com o motivo à mostra — não há como perguntar.
| Parâmetro | Padrão | Uso |
|---|---|---|
inactivityCheckHours |
24 |
Prazo para clicar num dos botões |
inactivityReturnDays |
3 |
Prazo para logar depois de dizer que ainda quer jogar |
/evento criar abre uma disputa por um período: durante a janela, o bot
contabiliza numa tabela secundária o quanto cada membro fez da métrica escolhida,
e no fim premia o pódio.
/evento criar nome:Corrida de Raids metrica:Guild Raids dias:14 premio:3 LE podio:3 pontos:500
/evento criar nome:Julho de Guerra metrica:Guerras dias:14 premio:2 LE inicio:29/07 00:00
| Opção | O que faz |
|---|---|
metrica |
Guild Raids, Guerras ou XP contribuído |
dias |
Duração (14 = duas semanas). Aceita fração para testes |
premio |
Texto livre, mostrado no painel e no anúncio |
inicio |
(Opcional) quando abre, horário de Brasília. Padrão: agora |
podio |
Quantos colocados são premiados (padrão: 1) |
pontos |
(Opcional) pontos da guilda para o 1º; cada posição abaixo leva metade |
canal |
Onde publicar o painel (padrão: o canal do comando) |
inicio aceita 29/07 00:00, 29/07/2026 00:00 e 2026-07-29 00:00 (sem hora
vira meia-noite). Enquanto não abre, o painel mostra a contagem regressiva e a
tabela fica vazia. Data no passado é recusada.
Todo mundo começa do zero: o evento conta só o que for feito depois da abertura. Nada do que a pessoa já tinha entra.
O watcher já detecta cada guild raid no instante em que ela termina (poller de 60s, o mesmo que anuncia a raid no canal). O evento é creditado ali, uma unidade para cada membro do grupo — não na apuração do dia seguinte.
É isso que faz um evento marcado para as 00:00 do dia 29 ser exato: a raid que terminou às 23:58 do dia 28 simplesmente não é creditada.
O preço: raid feita com o bot fora do ar não entra no evento. Os pontos
dessa raid não se perdem (a contagem diária recupera pelo guildRaids da API),
mas o placar do evento não a enxerga.
Essas duas não têm gatilho — só existem como delta da contagem diária
(progressSnapshot), então a granularidade é de um dia. A apuração lê a
janela do livro-razão de pontos (pointsEvents), que já registra quantidade
bruta com data. Por isso reapurar nunca duplica e /evento apurar pode rodar à
vontade.
Só que esse delta diário atravessa a hora da abertura: o lançamento do dia seguinte cobre desde a contagem anterior, incluindo horas em que o evento nem existia. Para não contar esse pedaço, o bot apura no momento em que o evento abre (na criação, ou pelo job de 5 em 5 minutos quando é agendado) e fixa o corte no instante real dessa apuração. Se a API do Wynncraft estiver fora do ar na hora, o comando avisa.
- A linha de base de quem entra na guilda no meio do evento fica de fora. Sem isso, um veterano recém-chegado largaria com uma vida inteira de XP e venceria sem jogar.
- Empate vai para quem chegou àquele número primeiro. Se empatar até no instante (comum na contagem diária, que carimba todo mundo com a mesma hora), decide o nome — só para o ranking não trocar de posição sozinho entre duas atualizações do painel.
pontosentra no livro-razão como concessão manual: aparece no/profile, conta para a inatividade e é reversível como qualquer outro lançamento.
O painel vive no canal events (ou no canal onde o comando foi usado) e se
reconstrói sozinho se alguém apagar a mensagem.
/giveaway criar premio:Mythic horas:48 vagas:2 requisito:Membro da guilda
A inscrição é o botão 🎉 Participar na própria mensagem — clicar de novo
sai do sorteio. Requisitos possíveis: aberto a todos, conta vinculada, membro da
guilda, ou pontos mínimos. O sorteio é apurado no minuto do vencimento; a staff
pode antecipar com /giveaway encerrar ou refazer com /giveaway reroll
(que exclui quem já ganhou).
- Healthcheck: HTTP em
:$PORT/health(use no health check do Easypanel). - Download do modpack: o mesmo servidor HTTP serve o
mods.rarem:$PORT/modpack. Exponha um domínio no Dokploy apontando para:$PORTe informe-o emPUBLIC_URL— o/modpackmonta o link${PUBLIC_URL}/modpack. - Dossiê GsW: o mesmo servidor abre a página em
:$PORT/gsw—${PUBLIC_URL}/gswno domínio público. É um HTML autocontido (os prints vão embutidos em base64), gerado porgsw/build.ps1direto emsrc/assets/. Editou o texto emgsw/template.htmlou trocou print emgsw/img/? Rodapowershell -ExecutionPolicy Bypass -File gsw/build.ps1e sobe de novo. - Backup: agende
scripts/backup.sh(mongodump gzip, mantém 14 dias). Veja o cabeçalho do script para as variáveis.
Variáveis de ambiente (veja .env.example):
| Variável | Descrição |
|---|---|
DISCORD_TOKEN |
Token do bot |
DISCORD_CLIENT_ID |
Application ID |
DISCORD_GUILD_ID |
ID do servidor Discord (registro instantâneo dos comandos) |
MONGO_URI |
String de conexão do MongoDB |
MONGO_DB |
Nome do banco (padrão: wynn_guild) |
WYNN_GUILD_PREFIX |
TAG da guilda na API |
WYNN_API_KEY |
(Opcional) chave da API v3 |
Habilite Server Members Intent no Developer Portal (Bot > Privileged Gateway Intents) — é necessário para o sync de cargos.
npm install
cp .env.example .env # preencha os valores
npm startApós subir, configure ao menos os cargos de classificação e o canal de registro:
/config role key:community role:@Comunidade
/config role key:guildMember role:@Membros WnBR
/config role key:banned role:@BANIDO
/config channel key:registration channel:#registro
Os cargos de liderança (votam nas candidaturas e podem usar /forcelink):
/config param key:voterRoles value:["<id_do_cargo>"]
- MongoDB: crie um serviço de MongoDB no Easypanel; copie a connection string
para
MONGO_URI. - Bot: crie um app a partir deste repositório (build via
Dockerfile). - Defina as variáveis de ambiente na aba do app (não precisa de
.envno servidor). - Deploy. Os slash commands se registram sozinhos no start.