Bot privado · Castelo Bruxo RP

CASTELO BRUXO RP

Organizado por:Castelo Bruxo

API v1 do Monstro: o servidor do jogo, o site e a staff organizam o Discord por aqui.

04API v1

Base: https://castelobruxorp.cntoficial.com.br/disc/v1 · JSON · chave por projeto

1. Autenticação e escopos

Toda chamada leva a chave do projeto no header x-api-key. Cada projeto (jogo, site, staff) tem sua chave e só usa as rotas dos seus escopos. Chaves são pedidas à staff do Castelo Bruxo RP e nunca vão para o navegador, commit ou log.

EscopoLibera
mensagensPOST /mensagens, POST /avisos, PATCH /avisos
dmPOST /dm
membros.lerGET /membros/:discordId
membros.editarPATCH /membros/:discordId (apelido)
casasPUT /membros/:discordId/casa
cargosPUT e DELETE /membros/:discordId/cargos/:cargo
wl.lerGET /wl, GET /wl/:discordId
wl.criarPOST /wl
wl.avaliaraprovar, reprovar e revogar WL
loginPOST /login/url, POST /login/trocar
organizacaoGET /organizacao, POST /organizacao/cargos

Hoje: jogo = mensagens, membros.ler, casas, wl.ler · site = login, wl.criar, wl.ler, membros.ler · staff = tudo.

Sucesso: { "ok": true, ... }. Erro: { "ok": false, "error": "<mensagem>" }. Limite de 120 chamadas por minuto por chave.

HTTPQuando
400campo inválido
401chave ausente ou errada
403chave sem o escopo; bot sem permissão no Discord (cargo do bot abaixo do cargo que ele mexe)
404usuário fora do servidor, pedido, canal ou mensagem inexistente
409WL duplicada ou já avaliada; cargo configurado que não existe
422usuário não aceita DM do bot
429limite por minuto
503bot conectando ou login com Discord sem configurar

Toda ação que muda algo fica registrada (com o projeto) e aparece no log de auditoria do Discord. Mande "staff": "Nome" no corpo para registrar quem fez, por exemplo o admin do jogo.

2. Mensagens e avisos

canal aceita o ID, um apelido configurado (ex.: avisos) ou o nome do canal.

POST /v1/mensagens

{ "canal": "avisos", "texto": "Servidor reiniciando em 5 minutos.", "cargos": ["<id do cargo>"], "allowEveryone": false }

Texto até 2000 caracteres. cargos (até 10) marca e notifica só esses cargos. Resposta: { "ok": true, "messageId": "..." }

POST /v1/avisos

{ "canal": "avisos", "texto": "Quadribol hoje às 20h!", "imagens": ["<base64>"], "cargos": [], "allowEveryone": false }

Até 4 imagens (PNG, JPEG, GIF, WEBP; 8 MB no total).

PATCH /v1/avisos

{ "canal": "avisos", "messageId": "...", "texto": "Quadribol remarcado para 21h." }

Edita só o texto de mensagem do próprio bot.

POST /v1/dm

{ "discordId": "...", "texto": "Sua carta chegou no correio coruja." }

3. Membros, casas e cargos

GET /v1/membros/:discordId

{ "ok": true, "membro": {
  "discordId": "...", "usuario": "harry", "nome": "Harry", "apelido": null, "avatar": "https://...",
  "noServidor": true, "entrouEm": "2026-11-02T12:00:00.000Z",
  "cargos": [{ "id": "...", "nome": "Grifinória" }],
  "casa": "gryffindor", "wl": true, "staff": false } }

Fora do servidor: { "discordId": "...", "noServidor": false }.

PATCH /v1/membros/:discordId

{ "apelido": "Harry Potter" }

Até 32 caracteres; null tira o apelido.

PUT /v1/membros/:discordId/casa

{ "casa": "gryffindor" }

Aceita as chaves do jogo (gryffindor, hufflepuff, ravenclaw, slytherin, as mesmas do hmp-houses) e os nomes em português (Grifinória, Lufa-Lufa, Corvinal, Sonserina). Tira as outras casas. { "casa": null } tira todas.

PUT · DELETE /v1/membros/:discordId/cargos/:cargo

Dá ou tira um cargo pelo apelido da lista de cargos gerenciáveis (ex.: monitor). Cargo fora da lista: 400; cargo de staff: 403. A API nunca mexe em cargo que não foi liberado.

4. Whitelist

Fluxo: o site manda o pedido → o Monstro publica no canal da staff com os botões Aprovar e Reprovar → a staff avalia pelo botão (ou pela API) → aprovado ganha o cargo de WL, recebe DM e é anunciado. Reprovado recebe DM com o motivo e pode pedir de novo. Só a staff usa os botões.

POST /v1/wl

{ "discordId": "...", "respostas": { "Nome do personagem": "Harry Potter", "História": "..." } }

De 1 a 30 respostas, até 1000 caracteres cada. O usuário precisa estar no servidor. 409 se já tem WL ou pedido pendente. Resposta: 201 { "ok": true, "pedido": { "id": 1, "status": "pendente", ... } }

GET /v1/wl/:discordId

{ "ok": true, "wl": { "discordId": "...", "noServidor": true, "aprovado": false, "pedido": { "id": 1, "status": "pendente", ... } } }

aprovado vem do cargo no Discord: é o que o servidor do jogo confere na entrada.

GET /v1/wl?status=pendente&pagina=1&porPagina=25

Lista os pedidos (pendente, aprovado, reprovado, revogado).

POST /v1/wl/pedidos/:id/aprovar · /v1/wl/pedidos/:id/reprovar

{ "motivo": "opcional, até 500", "staff": "Nome de quem avaliou" }

POST /v1/wl/:discordId/revogar

Tira o cargo de WL; o último pedido aprovado vira revogado.

5. Login com Discord (site)

O site faz o OAuth2 e o Monstro troca o code (só ele tem o Client Secret).

  1. POST /v1/login/url com { "redirectUri": "https://.../callback", "state": "<aleatório do site>" } → { "url": "https://discord.com/oauth2/authorize?..." }
  2. O site manda o usuário para essa URL. O Discord volta em redirectUri?code=...&state=....
  3. O site confere o state e chama POST /v1/login/trocar com { "code": "...", "redirectUri": "https://.../callback" } → { "ok": true, "perfil": { ... } }, o mesmo perfil de GET /membros, com casa, wl e staff.
  4. O site cria a própria sessão com o discordId.

O redirectUri precisa estar liberado pela staff (no Monstro e no Developer Portal).

6. Organização do servidor

GET /v1/organizacao

Servidor, cargos configurados (existe ou não, com ID) e canais resolvidos.

POST /v1/organizacao/cargos

Cria os cargos de WL e das casas que ainda não existem, com as cores das casas. Não mexe nos que já existem. Depois, o cargo do Monstro precisa ficar acima deles em Configurações → Cargos.

7. Exemplo (Node)

const MONSTRO = 'https://castelobruxorp.cntoficial.com.br/disc/v1';
async function monstro(caminho, { method = 'GET', body } = {}) {
  const res = await fetch(MONSTRO + caminho, {
    method,
    headers: { 'content-type': 'application/json', 'x-api-key': process.env.MONSTRO_API_KEY },
    body: body && JSON.stringify(body),
    signal: AbortSignal.timeout(10000),
  });
  const json = await res.json();
  if (!json.ok) throw Object.assign(new Error(json.error), { status: res.status });
  return json;
}

const { wl } = await monstro(`/wl/${discordId}`);
if (!wl.aprovado) recusarEntrada('Você ainda não tem whitelist no Castelo Bruxo RP.');
await monstro(`/membros/${discordId}/casa`, { method: 'PUT', body: { casa: 'gryffindor' } });

Falha do Discord nunca deve desfazer nada no jogo nem no site: registre e siga. Envio de mensagem não é idempotente: depois de timeout, confira o canal antes de reenviar.