aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Discord-bots

Discord log bot: serverevents loggen met discord.js

Een discord log bot is een van de waardevolste tools die je aan een server kunt toevoegen: hij registreert stilletjes wat er gebeurt en geeft je moderatieteam een degelijk auditspoor. Wie verwijderde welk bericht, wie verliet de server, wanneer kreeg iemand welke rol — dit alles in een vast kanaal kunnen zien, tilt discussies van "ik heb het niet gedaan" naar moderatie op basis van bewijs. In deze gids bouwen we met discord.js v14 een praktische bot die verwijderde berichten, leden die komen en gaan, en rolwijzigingen logt.

Wat doet een discord log bot?

Het ingebouwde auditlog van Discord bewaart alleen bepaalde beheerdersacties voor een beperkte tijd en toont nooit de inhoud van berichten. Wanneer je je eigen log bot schrijft, bepaal jij wat je registreert, hoe en waar. Typische toepassingen zijn:

  • Berichtcontrole: de inhoud van verwijderde en bewerkte berichten bewaren.
  • Ledenactiviteit: binnenkomsten en vertrekken volgen, samen met de accountleeftijd (waardevol voor raiddetectie).
  • Rechtenbewaking: het toekennen en intrekken van rollen zien, en wie dat deed.

We sturen al deze gebeurtenissen naar één #logs-kanaal als leesbare embeds.

Vereiste intents en partial-configuratie

Loggen vereist bevoorrechte (privileged) intents. GuildMembers is nodig voor ledengebeurtenissen en MessageContent om de inhoud van berichten te lezen. Je moet deze twee ook inschakelen in de instellingen van je applicatie in het Discord Developer Portal. Om gebeurtenissen voor oudere berichten die niet in de cache staan op te vangen, moet je partials declareren:

// index.js
const { Client, GatewayIntentBits, Partials } = require('discord.js');

const client = new Client({
  intents: [
    GatewayIntentBits.Guilds,
    GatewayIntentBits.GuildMembers,
    GatewayIntentBits.GuildMessages,
    GatewayIntentBits.MessageContent,
  ],
  partials: [Partials.Message, Partials.Channel, Partials.GuildMember],
});

const LOG_CHANNEL_ID = '123456789012345678';

client.login(process.env.TOKEN);

Om herhaling te vermijden, definiëren we een kleine hulpfunctie die we bij elke gebeurtenis hergebruiken:

const { EmbedBuilder } = require('discord.js');

function log(guild, embed) {
  const channel = guild.channels.cache.get(LOG_CHANNEL_ID);
  if (channel) channel.send({ embeds: [embed] }).catch(() => {});
}

Verwijderde en bewerkte berichten loggen

Verwijderingen vangen we op met de gebeurtenis Events.MessageDelete. Er is een belangrijke beperking: als een bericht dat vóór het starten van de bot is verstuurd — en niet in de cache staat — wordt verwijderd, komt de gebeurtenis alleen als partial binnen en kun je de inhoud niet herstellen, omdat het bericht niet meer op Discord bestaat. Daarom slaan we partial-berichten netjes over:

const { Events } = require('discord.js');

client.on(Events.MessageDelete, async (message) => {
  if (message.partial) return;          // inhoud niet beschikbaar als niet gecachet
  if (message.author?.bot) return;

  log(message.guild, new EmbedBuilder()
    .setColor(0xED4245)
    .setAuthor({ name: message.author.tag, iconURL: message.author.displayAvatarURL() })
    .setDescription(
      `🗑️ **Bericht verwijderd** — <#${message.channel.id}>\n` +
      (message.content || '*Geen inhoud*'),
    )
    .setTimestamp());
});

Bewerkingen beluisteren we met Events.MessageUpdate. Deze gebeurtenis vuurt ook af wanneer een linkvoorbeeld (embed) op een bericht laadt; om ruis te vermijden stoppen we vroeg als de inhoud niet echt is gewijzigd:

client.on(Events.MessageUpdate, async (oldMsg, newMsg) => {
  if (oldMsg.partial || newMsg.author?.bot) return;
  if (oldMsg.content === newMsg.content) return;  // alleen een embed geladen

  log(newMsg.guild, new EmbedBuilder()
    .setColor(0xFEE75C)
    .setAuthor({ name: newMsg.author.tag, iconURL: newMsg.author.displayAvatarURL() })
    .setDescription(`✏️ **Bericht bewerkt** — [Naar bericht](${newMsg.url})`)
    .addFields(
      { name: 'Voor', value: (oldMsg.content || '—').slice(0, 1024) },
      { name: 'Na', value: (newMsg.content || '—').slice(0, 1024) },
    )
    .setTimestamp());
});

Omdat een embed-veld maximaal 1024 tekens kan bevatten, knippen we af met slice(0, 1024); anders laten lange berichten het verzenden mislukken.

Binnenkomsten en vertrekken van leden loggen

Binnenkomsten komen via Events.GuildMemberAdd en vertrekken via Events.GuildMemberRemove. Tonen wanneer het account van een nieuwkomer is aangemaakt als relatieve tijdstempel met het <t:...:R>-formaat maakt het makkelijk om een raidgolf van gloednieuwe accounts te herkennen:

client.on(Events.GuildMemberAdd, (member) => {
  const created = Math.floor(member.user.createdTimestamp / 1000);
  log(member.guild, new EmbedBuilder()
    .setColor(0x57F287)
    .setAuthor({ name: member.user.tag, iconURL: member.user.displayAvatarURL() })
    .setDescription(`📥 **${member} heeft de server betreden**`)
    .addFields({ name: 'Accountleeftijd', value: `<t:${created}:R>` })
    .setTimestamp());
});

client.on(Events.GuildMemberRemove, (member) => {
  log(member.guild, new EmbedBuilder()
    .setColor(0xED4245)
    .setAuthor({ name: member.user.tag, iconURL: member.user.displayAvatarURL() })
    .setDescription(`📤 **${member.user.tag} heeft de server verlaten**`)
    .setTimestamp());
});

Rolwijzigingen loggen

Rolwijzigingen zijn geen aparte gebeurtenis; ze maken deel uit van de ledenupdate (Events.GuildMemberUpdate). Om de toegevoegde en verwijderde rollen te vinden, vergelijken we de rolcollecties van het oude en het nieuwe lid:

client.on(Events.GuildMemberUpdate, (oldMember, newMember) => {
  const oldRoles = oldMember.roles.cache;
  const newRoles = newMember.roles.cache;

  const added = newRoles.filter((r) => !oldRoles.has(r.id));
  const removed = oldRoles.filter((r) => !newRoles.has(r.id));
  if (added.size === 0 && removed.size === 0) return;  // bv. alleen bijnaam gewijzigd

  const lines = [];
  if (added.size) lines.push(`➕ Toegevoegd: ${added.map((r) => r).join(', ')}`);
  if (removed.size) lines.push(`➖ Verwijderd: ${removed.map((r) => r).join(', ')}`);

  log(newMember.guild, new EmbedBuilder()
    .setColor(0x5865F2)
    .setAuthor({ name: newMember.user.tag, iconURL: newMember.user.displayAvatarURL() })
    .setDescription(`🎭 **Rolwijziging** — ${newMember}\n${lines.join('\n')}`)
    .setTimestamp());
});

Wil je ook weten wie de actie uitvoerde, dan kun je het auditlog opvragen. Daarvoor heeft de bot het recht ViewAuditLog nodig:

const { AuditLogEvent } = require('discord.js');

const audit = await newMember.guild.fetchAuditLogs({
  type: AuditLogEvent.MemberRoleUpdate, limit: 1,
});
const entry = audit.entries.first();
const executor = entry?.target.id === newMember.id ? entry.executor : null;

Veelgestelde vragen

Waarom is de inhoud van sommige verwijderde berichten leeg?

Omdat dat bericht niet in de cache van de bot zat. discord.js cachet alleen berichten die hij tijdens het draaien ziet; als een ouder bericht dat vóór het starten van de bot is verstuurd wordt verwijderd, kan de inhoud niet worden hersteld. Dit is een beperking van de Discord-API, geen slechte code. Wil je een vollediger overzicht, dan moet je berichten in je eigen database opslaan.

Kan ik gebeurtenissen loggen die plaatsvinden terwijl de bot offline is?

Nee. Gateway-gebeurtenissen worden alleen live verstuurd zolang de bot verbonden is; alles wat tijdens downtime gebeurt, wordt gemist. Daarom is het belangrijk de log bot 24/7 draaiend te houden met een tool als PM2 en error-gebeurtenissen op te vangen.

Kan ik verschillende soorten gebeurtenissen over aparte kanalen verdelen?

Ja. In plaats van één LOG_CHANNEL_ID definieer je per gebeurtenistype verschillende kanaal-id's en geef je het doelkanaal als parameter mee aan de log()-functie; zo houd je berichtlogs gescheiden van ledenlogs.

Wil je een betrouwbare loginfrastructuur voor je server? Ik kan een Discord-bot ontwerpen en bouwen die van begin tot eind is afgestemd op je moderatiebehoeften. Neem contact op om over je project te praten.

Bu kategorideki tüm yazılar →

Devamı için