Aparência
Moderação
Um bot de moderação trabalha 24 horas sem cansar. Esta página cobre as ferramentas — e como usá-las sem transformar o bot numa brecha.
Antes de tudo
Todo comando de moderação precisa conferir se quem pediu tem a permissão. A API só confere o bot. Leia Duas perguntas diferentes.
As ferramentas
| Ação | Rota | Permissão do bot |
|---|---|---|
| Apagar mensagem | DELETE /groups/:id/channels/:cid/messages/:mid | manageMessages (as dos outros) |
| Tirar a reação de alguém | DELETE /groups/:id/channels/:cid/messages/:mid/reactions?emoji=&userId= | manageReactions (as dos outros) |
| Expulsar | POST /groups/:id/members/:userId/kick | kickMembers |
| Banir | POST /groups/:id/bans/:userId | banMembers |
| Desbanir | DELETE /groups/:id/bans/:userId | banMembers |
| Lista de banidos | GET /groups/:id/bans | banMembers |
| Dar/tirar cargos | PUT /groups/:id/members/:userId/roles | manageRoles |
As rotas de expulsar e banir não têm corpo — mande {}.
- Expulsar tira a pessoa do grupo; ela pode voltar com um convite.
- Banir tira e impede de voltar, até alguém desbanir. Dá para banir quem já saiu.
- Ninguém expulsa ou bane o dono, nem alguém com cargo igual ou acima do seu (
403). - A pessoa removida recebe
group-removedcomreason: "kicked"ou"banned".
Comandos de expulsar e banir
js
import { mention, mentionedIds } from "../golive/structures.js";
import { GoLiveApiError } from "../golive/rest.js";
export default {
name: "banir",
aliases: ["ban"],
description: "Bane alguém do grupo (não pode voltar até ser desbanido).",
usage: "!banir @pessoa [motivo]",
permission: "banMembers",
async run({ client, message }) {
const [targetId] = mentionedIds(message.text);
if (!targetId) return message.reply(`Mencione quem deve ser banido. Uso: ${this.usage}`);
if (targetId === client.user.id) return message.reply("Não vou me banir 😅");
try {
await client.rest.post(`/groups/${message.groupId}/bans/${targetId}`);
} catch (err) {
if (err instanceof GoLiveApiError && (err.status === 403 || err.status === 404)) {
return message.reply(`Não consegui: ${err.body?.error ?? "sem permissão"}`);
}
throw err;
}
const reason = message.text.replace(/^\S+\s*/, "").replace(/<@[^>]+>/g, "").trim();
return message.send(`🔨 ${mention(targetId)} foi banido(a).${reason ? ` Motivo: ${reason}` : ""}`);
},
};Moderação automática
O bot pode agir sozinho, sem comando, olhando cada mensagem que chega.
Filtro de links
js
const LINK = /(https?:\/\/|www\.)\S+/i;
const ALLOWED = ["golive.nemtudo.me", "youtube.com", "youtu.be"];
client.on("messageCreate", async (message) => {
if (message.author.bot) return;
const url = message.text.match(LINK)?.[0];
if (!url || ALLOWED.some((host) => url.includes(host))) return;
// Moderadores podem mandar o que quiserem
if (await client.hasPermission(message.groupId, message.author.id, "manageMessages")) return;
await message.delete();
await message.send(`🔗 <@${message.author.id}>, links externos não são permitidos aqui.`);
});Anti-flood
Muitas mensagens em pouco tempo, da mesma pessoa:
js
const WINDOW_MS = 10_000;
const MAX_MESSAGES = 6;
const recent = new Map(); // userId → timestamps
client.on("messageCreate", async (message) => {
if (message.author.bot) return;
const now = Date.now();
const times = (recent.get(message.author.id) ?? []).filter((t) => now - t < WINDOW_MS);
times.push(now);
recent.set(message.author.id, times);
if (times.length === MAX_MESSAGES) {
await message.reply("🐢 Devagar! Você está mandando mensagens rápido demais.");
} else if (times.length > MAX_MESSAGES + 4) {
await message.delete().catch(() => {});
}
});
// Limpa quem ficou quieto, para o Map não crescer para sempre
setInterval(() => {
const now = Date.now();
for (const [id, times] of recent) if (times.every((t) => now - t > WINDOW_MS)) recent.delete(id);
}, 60_000);Palavras proibidas do grupo
O Go Live já recusa algumas palavras em todo o site. Para uma lista própria do grupo:
js
const BLOCKED = ["palavrão1", "palavrão2"];
const normalize = (s) => s.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
client.on("messageCreate", async (message) => {
if (message.author.bot) return;
const text = normalize(message.text);
if (BLOCKED.some((word) => text.includes(word))) {
await message.delete();
await message.send(`🚫 <@${message.author.id}>, olha a linguagem.`);
}
});Avise, depois aja
Moderação automática erra. Comece só avisando (ou só registrando), veja os falsos positivos por uns dias e depois passe a apagar.
Sala de registros (logs)
Registrar o que o bot fez numa sala só da equipe dá transparência:
js
const LOG_CHANNEL = "l0gs4l4000001";
async function log(groupId, text) {
await client.sendMessage(groupId, LOG_CHANNEL, { text: `🧾 ${new Date().toLocaleString("pt-BR")} — ${text}` })
.catch((err) => console.error("log falhou:", err.message));
}
// dentro do !banir:
await log(message.groupId, `<@${message.author.id}> baniu <@${targetId}>. Motivo: ${reason || "—"}`);Deixe a sala visível só para a equipe com as permissões por sala.
Registrando mensagens apagadas
O evento group-message-deleted não traz o conteúdo. Para registrar o que foi apagado, o bot guarda as mensagens recentes quando chegam:
js
const recentMessages = new Map(); // id → { author, text }
client.on("messageCreate", (m) => {
recentMessages.set(m.id, { author: m.author.name, text: m.text, groupId: m.groupId });
if (recentMessages.size > 5000) recentMessages.delete(recentMessages.keys().next().value);
});
client.on("messageDelete", async ({ messageId }) => {
const m = recentMessages.get(messageId);
if (m) await log(m.groupId, `Mensagem de ${m.author} apagada: "${m.text.slice(0, 300)}"`);
});Privacidade
Guardar mensagens apagadas é sensível: a pessoa apagou por um motivo. Faça isso só em grupos que avisam que há registro, e só pelo tempo necessário.