Del 5 av 10 Moderat

Komplett Discord Bot Guide

Del 5: Event System

Fremgang: 5/10 (50%)

Del 5: Event System

Nå skal vi lære å lage en bot som reagerer på alt som skjer i serveren! Events er hjertet i Discord bots - de lar boten "høre" når ting skjer og reagere deretter.

Hva lærer vi i denne delen?

  1. Forstå Discord Events - Hva er events og hvordan fungerer de?
  2. Event handler system - Organisere events i egne filer
  3. Member join/leave events - Velkommen og farvel meldinger
  4. Message events - Reagere på meldinger og spam protection
  5. Guild events - Når boten legges til/fjernes fra servere
  6. Logging system - Holde styr på alt som skjer
  7. Auto-role system - Gi roller automatisk til nye medlemmer

Steg 1: Forstå Discord Events

Hva er Events?

Events er ting som skjer i Discord som boten din kan "høre":

Person kommer inn → memberAdd event → Bot sender velkommen melding
Noen sender melding → messageCreate event → Bot sjekker spam/filter
Person forlater → memberRemove event → Bot sender farvel melding  
Reaction legges til → messageReactionAdd event → Bot gir rolle

Viktige Events:

  • ready - Bot starter (bruker vi allerede)
  • guildMemberAdd - Noen kommer inn i serveren
  • guildMemberRemove - Noen forlater serveren
  • messageCreate - Ny melding sendes
  • messageDelete - Melding slettes
  • guildCreate - Bot legges til i ny server
  • interactionCreate - Slash command brukes (har vi allerede)

Steg 2: Event Handler System

La oss organisere events på samme måte som commands!

2.1: Lag events mappestruktur

events/
├── client/
│   ├── ready.js
│   └── interactionCreate.js
├── guild/
│   ├── guildMemberAdd.js
│   ├── guildMemberRemove.js
│   └── guildCreate.js
└── message/
    ├── messageCreate.js
    └── messageDelete.js

2.2: Flytt ready event

Lag events/client/ready.js:

javascript
module.exports = {
    name: 'ready',
    once: true, // Dette eventet kjører bare en gang
    execute(client) {
        console.log('='.repeat(50));
        console.log('BOT ER ONLINE!');
        console.log('='.repeat(50));
        console.log(`Bot navn: ${client.user.tag}`);
        console.log(`Bot ID: ${client.user.id}`);
        console.log(`Servere: ${client.guilds.cache.size}`);
        console.log(`Totalt brukere: ${client.users.cache.size}`);
        console.log('='.repeat(50));
        
        // Sett bot aktivitet
        client.user.setPresence({
            activities: [{ 
                name: `${client.guilds.cache.size} servere | /help for commands`,
                type: 'WATCHING'
            }],
            status: 'online'
        });
        
        // Log hvilke servere boten er i
        console.log('📋 Servere boten er i:');
        client.guilds.cache.forEach(guild => {
            console.log(`   • ${guild.name} (${guild.memberCount} medlemmer)`);
        });
    },
};

2.3: Flytt interactionCreate event

Lag events/client/interactionCreate.js:

javascript
module.exports = {
    name: 'interactionCreate',
    async execute(interaction) {
        if (!interaction.isChatInputCommand()) return;
        
        const command = interaction.client.commands.get(interaction.commandName);
        
        if (!command) {
            console.error(`❌ Ingen command kalt ${interaction.commandName} funnet.`);
            return;
        }
        
        try {
            await command.execute(interaction);
            console.log(`✅ ${interaction.user.tag} brukte /${interaction.commandName} i ${interaction.guild.name}`);
        } catch (error) {
            console.error('❌ Feil ved utføring av command:', error);
            
            const errorMessage = 'Det oppstod en feil ved utføring av denne kommandoen!';
            
            if (interaction.replied || interaction.deferred) {
                await interaction.followUp({ content: errorMessage, ephemeral: true });
            } else {
                await interaction.reply({ content: errorMessage, ephemeral: true });
            }
        }
    },
};

2.4: Oppdater index.js med Event Loader

Fjern de gamle event handlerne fra index.js og legg til dette:

javascript
// Last inn alle events
const eventsPath = path.join(__dirname, 'events');
const eventFolders = fs.readdirSync(eventsPath);

for (const folder of eventFolders) {
    const folderPath = path.join(eventsPath, folder);
    const eventFiles = fs.readdirSync(folderPath).filter(file => file.endsWith('.js'));
    
    for (const file of eventFiles) {
        const filePath = path.join(folderPath, file);
        const event = require(filePath);
        
        if (event.once) {
            client.once(event.name, (...args) => event.execute(...args));
        } else {
            client.on(event.name, (...args) => event.execute(...args));
        }
        
        console.log(`✅ Lastet event: ${event.name}`);
    }
}

👋 Steg 3: Member Join/Leave Events

3.1: Velkommen System

Lag events/guild/guildMemberAdd.js:

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

module.exports = {
    name: 'guildMemberAdd',
    async execute(member) {
        console.log(`👋 ${member.user.tag} ble med i ${member.guild.name}`);
        
        // Finn velkommen kanal (søk etter kanaler med "welcome", "velkommen", "general")
        const welcomeChannel = member.guild.channels.cache.find(channel => 
            channel.name.includes('welcome') || 
            channel.name.includes('velkommen') || 
            channel.name.includes('general')
        ) || member.guild.systemChannel;
        
        if (!welcomeChannel) {
            console.log('❌ Ingen velkommen kanal funnet');
            return;
        }
        
        // Lag fancy velkommen embed
        const welcomeEmbed = new EmbedBuilder()
            .setColor(0x2ecc71) // Grønn farge
            .setTitle('🎉 Velkommen til serveren!')
            .setDescription(`Hei ${member}! Vi er glad for å ha deg her! 🎊`)
            .setThumbnail(member.user.displayAvatarURL({ dynamic: true, size: 256 }))
            .addFields(
                { 
                    name: '👤 Ny medlem', 
                    value: `${member.user.tag}`, 
                    inline: true 
                },
                { 
                    name: '📅 Konto opprettet', 
                    value: `<t:${Math.floor(member.user.createdTimestamp / 1000)}:R>`, 
                    inline: true 
                },
                { 
                    name: '🔢 Medlem nummer', 
                    value: `${member.guild.memberCount}`, 
                    inline: true 
                },
                {
                    name: '📋 Kom i gang',
                    value: '• Les <#KANAL_ID> for regler\n• Bruk `/help` for bot commands\n• Ha det gøy! 🎮',
                    inline: false
                }
            )
            .setImage('https://media.giphy.com/media/Nx0rz3jtxtEre/giphy.gif') // Velkommen GIF
            .setFooter({ 
                text: `${member.guild.name} • Medlem ${member.guild.memberCount}`,
                iconURL: member.guild.iconURL()
            })
            .setTimestamp();
        
        try {
            await welcomeChannel.send({ 
                content: `${member} 👋`, // Pinger den nye brukeren
                embeds: [welcomeEmbed] 
            });
        } catch (error) {
            console.error('❌ Kunne ikke sende velkommen melding:', error);
        }
        
        // Auto-role (legger til standardrolle)
        const autoRole = member.guild.roles.cache.find(role => 
            role.name.toLowerCase() === 'medlem' || 
            role.name.toLowerCase() === 'member'
        );
        
        if (autoRole) {
            try {
                await member.roles.add(autoRole);
                console.log(`✅ Ga ${member.user.tag} rollen "${autoRole.name}"`);
            } catch (error) {
                console.error('❌ Kunne ikke gi auto-rolle:', error);
            }
        }
    },
};

3.2: Farvel System

Lag events/guild/guildMemberRemove.js:

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

module.exports = {
    name: 'guildMemberRemove',
    async execute(member) {
        console.log(`👋 ${member.user.tag} forlot ${member.guild.name}`);
        
        // Finn log kanal eller bruk samme som velkommen
        const logChannel = member.guild.channels.cache.find(channel => 
            channel.name.includes('log') || 
            channel.name.includes('goodbye') || 
            channel.name.includes('farvel') ||
            channel.name.includes('general')
        ) || member.guild.systemChannel;
        
        if (!logChannel) return;
        
        // Lag farvel embed
        const goodbyeEmbed = new EmbedBuilder()
            .setColor(0xe74c3c) // Rød farge  
            .setTitle('👋 Medlem forlot serveren')
            .setDescription(`**${member.user.tag}** har forlatt serveren.`)
            .setThumbnail(member.user.displayAvatarURL({ dynamic: true }))
            .addFields(
                { 
                    name: '👤 Bruker', 
                    value: `${member.user.tag}\n\`${member.user.id}\``, 
                    inline: true 
                },
                { 
                    name: '📅 Var medlem i', 
                    value: `<t:${Math.floor(member.joinedTimestamp / 1000)}:R>`, 
                    inline: true 
                },
                { 
                    name: '🔢 Totalt medlemmer', 
                    value: `${member.guild.memberCount}`, 
                    inline: true 
                }
            )
            .setFooter({ 
                text: member.guild.name,
                iconURL: member.guild.iconURL()
            })
            .setTimestamp();
        
        try {
            await logChannel.send({ embeds: [goodbyeEmbed] });
        } catch (error) {
            console.error('❌ Kunne ikke sende farvel melding:', error);
        }
    },
};

💬 Steg 4: Message Events

4.1: Message Logging

Lag events/message/messageCreate.js:

javascript
module.exports = {
    name: 'messageCreate',
    async execute(message) {
        // Ignorer bot meldinger
        if (message.author.bot) return;
        
        // Log alle meldinger (for debugging - kan fjernes senere)
        console.log(`💬 ${message.author.tag} i #${message.channel.name}: ${message.content.substring(0, 50)}${message.content.length > 50 ? '...' : ''}`);
        
        // Enkel spam protection
        if (await isSpamMessage(message)) {
            try {
                await message.delete();
                console.log(`🗑️ Slettet spam melding fra ${message.author.tag}`);
                
                // Send warning (ephemeral via DM eller channel)
                const warningChannel = message.channel;
                await warningChannel.send({
                    content: `⚠️ ${message.author}, ikke spam takk!`,
                    allowedMentions: { users: [message.author.id] }
                });
                
            } catch (error) {
                console.error('❌ Kunne ikke slette spam melding:', error);
            }
        }
        
        // Auto-react på spesielle meldinger
        if (message.content.toLowerCase().includes('takk')) {
            message.react('❤️').catch(console.error);
        }
        
        if (message.content.toLowerCase().includes('bot')) {
            message.react('🤖').catch(console.error);
        }
    },
};

// Enkel spam detection
async function isSpamMessage(message) {
    const content = message.content.toLowerCase();
    
    // Sjekk for caps spam (over 70% store bokstaver)
    const capsPercentage = (content.match(/[A-Z]/g) || []).length / content.length;
    if (capsPercentage > 0.7 && content.length > 10) return true;
    
    // Sjekk for repeated karakterer
    if (/(.)\1{4,}/.test(content)) return true;
    
    // Sjekk for spam keywords
    const spamKeywords = ['free nitro', 'discord.gift', 'click here', 'dm me'];
    if (spamKeywords.some(keyword => content.includes(keyword))) return true;
    
    return false;
}

4.2: Message Delete Logging

Lag events/message/messageDelete.js:

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

module.exports = {
    name: 'messageDelete',
    async execute(message) {
        // Ignorer bot meldinger
        if (message.author?.bot) return;
        
        console.log(`🗑️ Melding slettet i #${message.channel.name} av ${message.author?.tag || 'Ukjent'}`);
        
        // Finn log kanal
        const logChannel = message.guild.channels.cache.find(channel => 
            channel.name.includes('log') || 
            channel.name.includes('deleted') ||
            channel.name.includes('mod')
        );
        
        if (!logChannel) return;
        
        // Lag delete log embed
        const deleteEmbed = new EmbedBuilder()
            .setColor(0xf39c12) // Orange farge
            .setTitle('🗑️ Melding Slettet')
            .addFields(
                { 
                    name: '👤 Forfatter', 
                    value: message.author ? `${message.author.tag}\n\`${message.author.id}\`` : 'Ukjent', 
                    inline: true 
                },
                { 
                    name: '📍 Kanal', 
                    value: `${message.channel}\n\`${message.channel.id}\``, 
                    inline: true 
                },
                { 
                    name: '🕐 Tidspunkt', 
                    value: `<t:${Math.floor(Date.now() / 1000)}:F>`, 
                    inline: true 
                }
            )
            .setTimestamp();
        
        // Legg til meldings innhold hvis det finnes
        if (message.content) {
            deleteEmbed.addFields({
                name: '💬 Innhold',
                value: message.content.length > 1024 ? 
                    message.content.substring(0, 1021) + '...' : 
                    message.content,
                inline: false
            });
        }
        
        // Legg til vedlegg info
        if (message.attachments.size > 0) {
            const attachmentInfo = message.attachments.map(att => att.name).join(', ');
            deleteEmbed.addFields({
                name: '📎 Vedlegg',
                value: attachmentInfo,
                inline: false
            });
        }
        
        try {
            await logChannel.send({ embeds: [deleteEmbed] });
        } catch (error) {
            console.error('❌ Kunne ikke logge slettet melding:', error);
        }
    },
};

🏠 Steg 5: Guild Events

5.1: Bot Lagt til i Ny Server

Lag events/guild/guildCreate.js:

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

module.exports = {
    name: 'guildCreate',
    async execute(guild) {
        console.log(`🎉 Lagt til i ny server: ${guild.name} (${guild.memberCount} medlemmer)`);
        
        // Oppdater bot presence
        guild.client.user.setPresence({
            activities: [{ 
                name: `${guild.client.guilds.cache.size} servere | /help for commands`,
                type: 'WATCHING'
            }],
            status: 'online'
        });
        
        // Send velkommen melding til eieren
        try {
            const owner = await guild.fetchOwner();
            
            const welcomeEmbed = new EmbedBuilder()
                .setColor(0x2ecc71)
                .setTitle('🎉 Takk for at du la til boten!')
                .setDescription('Hei! Jeg er din nye Discord bot. Her er noen tips for å komme i gang:')
                .addFields(
                    {
                        name: '⚡ Kom i gang',
                        value: '• Bruk `/help` for å se alle commands\n• Sett opp velkommen kanal (navngi den "welcome" eller "velkommen")\n• Lag en rolle som heter "medlem" for auto-role',
                        inline: false
                    },
                    {
                        name: '🛠️ Grunnleggende oppsett',
                        value: '• Gi boten "Manage Roles" tillatelse for auto-role\n• Gi "Manage Messages" for spam protection\n• Lag en #log kanal for aktivitets logging',
                        inline: false
                    },
                    {
                        name: '💡 Tips',
                        value: 'Jeg reagerer automatisk på meldinger og holder øye med serveren din. Bruk `/status` kommandoen (admin) for å endre min aktivitet!',
                        inline: false
                    }
                )
                .setThumbnail(guild.client.user.displayAvatarURL())
                .setFooter({ 
                    text: 'Ha det gøy med boten din! 🚀',
                    iconURL: guild.iconURL()
                })
                .setTimestamp();
            
            await owner.send({ embeds: [welcomeEmbed] });
            
        } catch (error) {
            console.log('❌ Kunne ikke sende velkommen DM til eier:', error.message);
            
            // Prøv å sende i system channel i stedet
            if (guild.systemChannel) {
                try {
                    await guild.systemChannel.send({
                        content: `👋 Hei ${guild.name}! Bruk \`/help\` for å se alle mine commands!`
                    });
                } catch (systemError) {
                    console.log('❌ Kunne ikke sende velkommen melding i system channel');
                }
            }
        }
        
        // Finn og hilse på i general channel
        const generalChannel = guild.channels.cache.find(channel => 
            channel.name.includes('general') || 
            channel.name.includes('chat') ||
            channel.name.includes('main')
        );
        
        if (generalChannel && generalChannel.permissionsFor(guild.members.me).has('SendMessages')) {
            try {
                await generalChannel.send('👋 Hei alle sammen! Jeg er ny her. Bruk `/help` for å se hva jeg kan gjøre! 🤖');
            } catch (error) {
                console.log('❌ Kunne ikke sende hilsen i general channel');
            }
        }
    },
};

🧪 Steg 6: Testing Event System

6.1: Start Bot med Ny Event System

bash
node index.js

Du skal se:

✅ Lastet event: ready
✅ Lastet event: interactionCreate
✅ Lastet event: guildMemberAdd
✅ Lastet event: guildMemberRemove  
✅ Lastet event: messageCreate
✅ Lastet event: messageDelete
✅ Lastet event: guildCreate

6.2: Test Events

Velkommen/Farvel:

  1. Invite en venn til serveren (eller bruk alt account)
  2. Se at bot sender velkommen melding
  3. La personen forlate serveren
  4. Se farvel log melding

Message Events:

  1. Send normale meldinger - se logging i konsoll
  2. Send "takk" - bot skal reagere med ❤️
  3. Send "bot" - bot skal reagere med 🤖
  4. Test spam (send CAPS MELDING eller aaaaaaa)
  5. Slett en melding - sjekk log channel

🎨 Steg 7: Avanserte Features

7.1: XP System (enkelt)

Legg til i events/message/messageCreate.js:

javascript
// Legg til øverst i filen
const userXP = new Map(); // Midlertidig lagring - vi lager database senere

// Legg til i execute funksjonen
async function execute(message) {
    if (message.author.bot) return;
    
    // ... eksisterende kode ...
    
    // XP system
    const userId = message.author.id;
    const currentXP = userXP.get(userId) || 0;
    const xpGain = Math.floor(Math.random() * 10) + 1; // 1-10 XP per melding
    
    userXP.set(userId, currentXP + xpGain);
    
    // Sjekk for level up (hver 100 XP = 1 level)
    const newLevel = Math.floor((currentXP + xpGain) / 100);
    const oldLevel = Math.floor(currentXP / 100);
    
    if (newLevel > oldLevel) {
        message.react('🎉');
        message.reply(`🎉 Gratulerer ${message.author}! Du nådde level ${newLevel}! 🎉`);
        console.log(`🎉 ${message.author.tag} nådde level ${newLevel}`);
    }
}

7.2: Reaction Role System

Lag events/message/messageReactionAdd.js:

javascript
module.exports = {
    name: 'messageReactionAdd',
    async execute(reaction, user) {
        // Ignorer bot reactions
        if (user.bot) return;
        
        // Handle partial reactions
        if (reaction.partial) {
            try {
                await reaction.fetch();
            } catch (error) {
                console.error('Could not fetch reaction:', error);
                return;
            }
        }
        
        // Reaction role system - sjekk for spesifikke meldinger/reactions
        const guild = reaction.message.guild;
        const member = guild.members.cache.get(user.id);
        
        // Eksempel: 🎮 gir "Gamer" rolle
        if (reaction.emoji.name === '🎮') {
            const gamerRole = guild.roles.cache.find(role => role.name === 'Gamer');
            if (gamerRole && !member.roles.cache.has(gamerRole.id)) {
                try {
                    await member.roles.add(gamerRole);
                    console.log(`✅ Ga ${user.tag} Gamer rollen via reaction`);
                } catch (error) {
                    console.error('❌ Kunne ikke gi rolle:', error);
                }
            }
        }
        
        // Eksempel: 🎵 gir "Music Lover" rolle
        if (reaction.emoji.name === '🎵') {
            const musicRole = guild.roles.cache.find(role => role.name === 'Music Lover');
            if (musicRole && !member.roles.cache.has(musicRole.id)) {
                try {
                    await member.roles.add(musicRole);
                    console.log(`✅ Ga ${user.tag} Music Lover rollen via reaction`);
                } catch (error) {
                    console.error('❌ Kunne ikke gi rolle:', error);
                }
            }
        }
    },
};

✅ Oppsummering

Hva har vi lært:

  • ✅ Organisere events i strukturerte filer
  • ✅ Velkommen/farvel system med fancy embeds
  • ✅ Auto-role assignment
  • ✅ Message logging og spam protection
  • ✅ Server event håndtering
  • ✅ Reaction systems
  • ✅ Enkel XP tracking

Event system gir oss:

  • 🎉 Levende server - Bot reagerer på alt som skjer
  • 📊 Logging - Hold styr på aktivitet
  • 🛡️ Moderasjon - Automatisk spam protection
  • 👋 Velkomst - Nye medlemmer føler seg velkommen
  • 🎮 Interaksjon - Auto-reactions og rolle system

🔧 Neste Forbedringer

I neste del (Database System) flytter vi XP og annen data fra midlertidig lagring til permanent database. Da kan boten huske ting selv når den restarter!

🚀 Neste Steg

I del 6 skal vi lære om Database System med MySQL! Vi lærer:

  • Sette opp MySQL database
  • Lagre brukerdata permanent
  • XP system som huskes
  • Ban/kick historikk
  • User profiles og statistikk
  • Database sikkerhet

Klar for å gi boten permanent hukommelse?