Del 7 av 10 Moderat

Komplett Discord Bot Guide

Del 7: Staff System med Moderasjon

Fremgang: 7/10 (70%)

Del 7: Staff System med Moderasjon

Nå skal vi lage et komplett staff system som lar moderatorer og admins administrere serveren gjennom boten! Dette inkluderer kick, ban, warning system og mye mer.

Hva lærer vi i denne delen?

  1. Staff rolle hierarki - Hvem kan gjøre hva?
  2. Permission system - Rollbasert tilgangskontroll
  3. Kick/Ban commands - Med logging til database
  4. Warning system - Gi advarsler til brukere
  5. Timeout commands - Midlertidig mute brukere
  6. Staff utilities - Nyttige moderator verktøy
  7. Audit logging - Spore alle staff handlinger

Steg 1: Staff Rolle Hierarki

1.1: Forstå Staff Struktur

La oss lage et system med 3 staff nivåer:

ADMIN (Administrator permission)
├── Alle moderator funksjoner
├── Permanent ban/unban
├── Server innstillinger
└── Staff management

MODERATOR (Moderate Members permission) 
├── Kick/timeout brukere
├── Temporary bans (24 timer)  
├── Warning system
└── Message management

HELPER (Manage Messages permission)
├── Slette meldinger
├── Basic warnings  
└── Server info commands

1.2: Lag permissions helper

Lag utils/permissions.js:

javascript
class PermissionChecker {
    constructor() {
        this.staffRoles = {
            ADMIN: ['Administrator'],
            MODERATOR: ['Moderate Members', 'Kick Members', 'Ban Members'],
            HELPER: ['Manage Messages']
        };
    }
    
    // Sjekk om bruker har spesifikk staff rolle
    hasStaffRole(member, level) {
        switch(level.toUpperCase()) {
            case 'ADMIN':
                return member.permissions.has('Administrator');
            case 'MODERATOR':
                return member.permissions.has('ModerateMembers') || 
                       member.permissions.has('KickMembers') ||
                       member.permissions.has('BanMembers') ||
                       member.permissions.has('Administrator');
            case 'HELPER':
                return member.permissions.has('ManageMessages') ||
                       member.permissions.has('ModerateMembers') ||
                       member.permissions.has('Administrator');
            default:
                return false;
        }
    }
    
    // Få høyeste staff nivå for bruker
    getStaffLevel(member) {
        if (this.hasStaffRole(member, 'ADMIN')) return 'ADMIN';
        if (this.hasStaffRole(member, 'MODERATOR')) return 'MODERATOR';  
        if (this.hasStaffRole(member, 'HELPER')) return 'HELPER';
        return null;
    }
    
    // Sjekk om bruker kan moderate target
    canModerate(staffMember, targetMember) {
        // Kan ikke moderate seg selv
        if (staffMember.id === targetMember.id) return false;
        
        // Kan ikke moderate bot eiere
        if (targetMember.guild.ownerId === targetMember.id) return false;
        
        // Sjekk rolle hierarki
        const staffHighestRole = staffMember.roles.highest;
        const targetHighestRole = targetMember.roles.highest;
        
        return staffHighestRole.position > targetHighestRole.position;
    }
    
    // Format staff level for display
    formatStaffLevel(level) {
        const formats = {
            'ADMIN': 'Administrator',
            'MODERATOR': 'Moderator',  
            'HELPER': '👥 Helper'
        };
        return formats[level] || '❌ Ingen staff rolle';
    }
}

module.exports = new PermissionChecker();

🔨 Steg 2: Kick Command

2.1: Lag kick.js

Lag commands/moderation/kick.js:

javascript
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const permissions = require('../../utils/permissions.js');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('kick')
        .setDescription('Kick en bruker fra serveren')
        .addUserOption(option =>
            option.setName('bruker')
                .setDescription('Brukeren som skal kickes')
                .setRequired(true)
        )
        .addStringOption(option =>
            option.setName('grunn')
                .setDescription('Grunn til kick')
                .setRequired(false)
                .setMaxLength(500)
        ),
    
    async execute(interaction) {
        // Sjekk staff permissions
        if (!permissions.hasStaffRole(interaction.member, 'MODERATOR')) {
            return interaction.reply({
                content: '❌ Du trenger moderator tillatelser for å bruke denne kommandoen.',
                ephemeral: true
            });
        }
        
        const target = interaction.options.getUser('bruker');
        const reason = interaction.options.getString('grunn') || 'Ingen grunn oppgitt';
        
        // Få target som guild member
        let targetMember;
        try {
            targetMember = await interaction.guild.members.fetch(target.id);
        } catch (error) {
            return interaction.reply({
                content: '❌ Kunne ikke finne denne brukeren i serveren.',
                ephemeral: true
            });
        }
        
        // Sjekk om vi kan moderate target
        if (!permissions.canModerate(interaction.member, targetMember)) {
            return interaction.reply({
                content: '❌ Du kan ikke kicke denne brukeren (høyere rolle eller server eier).',
                ephemeral: true
            });
        }
        
        // Sjekk bot permissions
        if (!interaction.guild.members.me.permissions.has('KickMembers')) {
            return interaction.reply({
                content: '❌ Jeg har ikke tillatelse til å kicke medlemmer.',
                ephemeral: true
            });
        }
        
        // Kan ikke kicke boten selv
        if (targetMember.id === interaction.client.user.id) {
            return interaction.reply({
                content: '❌ Jeg kan ikke kicke meg selv! 🤖',
                ephemeral: true
            });
        }
        
        await interaction.deferReply();
        
        try {
            // Send DM til bruker før kick
            const dmEmbed = new EmbedBuilder()
                .setColor(0xf39c12)
                .setTitle('⚠️ Du har blitt kicket')
                .addFields(
                    { name: '🏠 Server', value: interaction.guild.name, inline: true },
                    { name: '👮 Moderator', value: interaction.user.tag, inline: true },
                    { name: '📝 Grunn', value: reason, inline: false },
                    { name: '🔄 Kan du komme tilbake?', value: 'Ja, du kan joine serveren igjen hvis du har invite link.', inline: false }
                )
                .setThumbnail(interaction.guild.iconURL())
                .setTimestamp();
            
            try {
                await target.send({ embeds: [dmEmbed] });
            } catch (dmError) {
                console.log(`❌ Kunne ikke sende DM til ${target.tag}`);
            }
            
            // Utfør kick
            await targetMember.kick(reason);
            
            // Logg til database
            interaction.client.database.addModLog(
                interaction.guild.id,
                target.id,
                interaction.user.id,
                'kick',
                reason
            );
            
            // Lag success embed
            const successEmbed = new EmbedBuilder()
                .setColor(0xf39c12)
                .setTitle('👢 Bruker Kicket')
                .addFields(
                    { name: '👤 Bruker', value: `${target.tag}\n\`${target.id}\``, inline: true },
                    { name: '👮 Moderator', value: `${interaction.user.tag}`, inline: true },
                    { name: '📝 Grunn', value: reason, inline: false }
                )
                .setThumbnail(target.displayAvatarURL())
                .setFooter({
                    text: `${interaction.guild.name} Moderasjon`,
                    iconURL: interaction.guild.iconURL()
                })
                .setTimestamp();
            
            await interaction.editReply({ embeds: [successEmbed] });
            
            // Log til server sin log kanal
            await interaction.client.database.logToChannel(
                interaction.client,
                interaction.guild.id,
                `${target.tag} ble kicket av ${interaction.user.tag}: ${reason}`,
                0xe74c3c
            );
            
            console.log(`KICK: ${interaction.user.tag} kicket ${target.tag} fra ${interaction.guild.name}: ${reason}`);
            
        } catch (error) {
            console.error('❌ Kick feil:', error);
            
            await interaction.editReply({
                content: '❌ Kunne ikke kicke brukeren. Sjekk at boten har høyere rolle enn brukeren.',
                ephemeral: true
            });
        }
    },
};

🔒 Steg 3: Ban Command

3.1: Lag ban.js

Lag commands/moderation/ban.js:

javascript
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const permissions = require('../../utils/permissions.js');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('ban')
        .setDescription('Ban en bruker fra serveren')
        .addUserOption(option =>
            option.setName('bruker')
                .setDescription('Brukeren som skal bannes')
                .setRequired(true)
        )
        .addStringOption(option =>
            option.setName('grunn')
                .setDescription('Grunn til ban')
                .setRequired(false)
                .setMaxLength(500)
        )
        .addIntegerOption(option =>
            option.setName('slett_dager')
                .setDescription('Hvor mange dager med meldinger som skal slettes (0-7)')
                .setRequired(false)
                .setMinValue(0)
                .setMaxValue(7)
        )
        .addIntegerOption(option =>
            option.setName('varighet')
                .setDescription('Ban varighet i timer (tom = permanent)')
                .setRequired(false)
                .setMinValue(1)
                .setMaxValue(168) // 7 dager max for moderators
        ),
    
    async execute(interaction) {
        // Sjekk permissions  
        if (!permissions.hasStaffRole(interaction.member, 'MODERATOR')) {
            return interaction.reply({
                content: '❌ Du trenger moderator tillatelser for å bruke denne kommandoen.',
                ephemeral: true
            });
        }
        
        const target = interaction.options.getUser('bruker');
        const reason = interaction.options.getString('grunn') || 'Ingen grunn oppgitt';
        const deleteMessageDays = interaction.options.getInteger('slett_dager') || 0;
        const duration = interaction.options.getInteger('varighet');
        
        // Sjekk ban duration permissions
        if (duration && !permissions.hasStaffRole(interaction.member, 'ADMIN')) {
            if (duration > 24) {
                return interaction.reply({
                    content: '❌ Moderatorer kan kun gi temporary bans opptil 24 timer. Admins kan gi lengre/permanente bans.',
                    ephemeral: true
                });
            }
        }
        
        // Få target member hvis de er i serveren
        let targetMember = null;
        try {
            targetMember = await interaction.guild.members.fetch(target.id);
            
            // Sjekk moderation permissions
            if (!permissions.canModerate(interaction.member, targetMember)) {
                return interaction.reply({
                    content: '❌ Du kan ikke banne denne brukeren (høyere rolle eller server eier).',
                    ephemeral: true
                });
            }
        } catch (error) {
            // Bruker er ikke i serveren - det er ok, vi kan fortsatt banne ID
        }
        
        // Sjekk bot permissions
        if (!interaction.guild.members.me.permissions.has('BanMembers')) {
            return interaction.reply({
                content: '❌ Jeg har ikke tillatelse til å banne medlemmer.',
                ephemeral: true
            });
        }
        
        if (target.id === interaction.client.user.id) {
            return interaction.reply({
                content: '❌ Jeg kan ikke banne meg selv! 🤖',
                ephemeral: true
            });
        }
        
        await interaction.deferReply();
        
        try {
            // Send DM til bruker før ban (hvis de er i serveren)
            if (targetMember) {
                const dmEmbed = new EmbedBuilder()
                    .setColor(0xe74c3c)
                    .setTitle('🔨 Du har blitt bannet')
                    .addFields(
                        { name: '🏠 Server', value: interaction.guild.name, inline: true },
                        { name: '👮 Moderator', value: interaction.user.tag, inline: true },
                        { name: '📝 Grunn', value: reason, inline: false },
                        { 
                            name: '⏰ Varighet', 
                            value: duration ? `${duration} timer` : 'Permanent', 
                            inline: true 
                        }
                    )
                    .setThumbnail(interaction.guild.iconURL())
                    .setTimestamp();
                
                try {
                    await target.send({ embeds: [dmEmbed] });
                } catch (dmError) {
                    console.log(`❌ Kunne ikke sende DM til ${target.tag}`);
                }
            }
            
            // Utfør ban
            await interaction.guild.members.ban(target.id, {
                reason: `${reason} | Moderator: ${interaction.user.tag}`,
                deleteMessageDays: deleteMessageDays
            });
            
            // Logg til database
            interaction.client.database.addModLog(
                interaction.guild.id,
                target.id,
                interaction.user.id,
                duration ? 'tempban' : 'ban',
                reason,
                duration
            );
            
            // Schedule unban hvis temporary
            if (duration) {
                setTimeout(async () => {
                    try {
                        await interaction.guild.members.unban(target.id, 'Temporary ban expired');
                        console.log(`⏰ Auto-unbanned ${target.tag} etter ${duration} timer`);
                        
                        // Logg unban
                        interaction.client.database.addModLog(
                            interaction.guild.id,
                            target.id,
                            interaction.client.user.id, // Bot ID
                            'unban',
                            'Temporary ban expired'
                        );
                        
                    } catch (unbanError) {
                        console.error('❌ Auto-unban feil:', unbanError);
                    }
                }, duration * 60 * 60 * 1000); // timer til millisekunder
            }
            
            // Success embed
            const successEmbed = new EmbedBuilder()
                .setColor(0xe74c3c)
                .setTitle('🔨 Bruker Bannet')
                .addFields(
                    { name: '👤 Bruker', value: `${target.tag}\n\`${target.id}\``, inline: true },
                    { name: '👮 Moderator', value: `${interaction.user.tag}`, inline: true },
                    { name: '⏰ Varighet', value: duration ? `${duration} timer` : 'Permanent', inline: true },
                    { name: '📝 Grunn', value: reason, inline: false }
                )
                .setThumbnail(target.displayAvatarURL())
                .setFooter({
                    text: `Slettet ${deleteMessageDays} dager med meldinger`,
                    iconURL: interaction.guild.iconURL()
                })
                .setTimestamp();
            
            await interaction.editReply({ embeds: [successEmbed] });
            
            // Log til server sin log kanal
            await interaction.client.database.logToChannel(
                interaction.client,
                interaction.guild.id,
                `${target.tag} ble bannet av ${interaction.user.tag}${duration ? ` (${duration}h)` : ' (permanent)'}: ${reason}`,
                0xe74c3c
            );
            
            console.log(`BAN: ${interaction.user.tag} bannet ${target.tag} fra ${interaction.guild.name} (${duration ? duration + 'h' : 'permanent'}): ${reason}`);
            
        } catch (error) {
            console.error('❌ Ban feil:', error);
            
            await interaction.editReply({
                content: '❌ Kunne ikke banne brukeren. Sjekk at boten har høyere rolle og riktige tillatelser.',
                ephemeral: true
            });
        }
    },
};

⚠️ Steg 4: Warning System

4.1: Oppdater database for warnings

Legg til i database/database.js - i init() funksjonen:

javascript
// Warnings tabell
this.db.exec(`
    CREATE TABLE IF NOT EXISTS warnings (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        guild_id TEXT NOT NULL,
        user_id TEXT NOT NULL,
        moderator_id TEXT NOT NULL,
        reason TEXT NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )
`);

Legg til warning functions:

javascript
// Warning functions
addWarning(guildId, userId, moderatorId, reason) {
    const stmt = this.db.prepare(`
        INSERT INTO warnings (guild_id, user_id, moderator_id, reason)
        VALUES (?, ?, ?, ?)
    `);
    return stmt.run(guildId, userId, moderatorId, reason);
}

getWarnings(guildId, userId) {
    const stmt = this.db.prepare(`
        SELECT * FROM warnings 
        WHERE guild_id = ? AND user_id = ?
        ORDER BY created_at DESC
    `);
    return stmt.all(guildId, userId);
}

removeWarning(warningId) {
    const stmt = this.db.prepare('DELETE FROM warnings WHERE id = ?');
    return stmt.run(warningId);
}

getWarningCount(guildId, userId) {
    const stmt = this.db.prepare(`
        SELECT COUNT(*) as count 
        FROM warnings 
        WHERE guild_id = ? AND user_id = ?
    `);
    return stmt.get(guildId, userId).count;
}

4.2: Lag warn command

Lag commands/moderation/warn.js:

javascript
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const permissions = require('../../utils/permissions.js');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('warn')
        .setDescription('Gi en advarsel til en bruker')
        .addUserOption(option =>
            option.setName('bruker')
                .setDescription('Brukeren som skal advares')
                .setRequired(true)
        )
        .addStringOption(option =>
            option.setName('grunn')
                .setDescription('Grunn til advarsel')
                .setRequired(true)
                .setMaxLength(500)
        ),
    
    async execute(interaction) {
        if (!permissions.hasStaffRole(interaction.member, 'HELPER')) {
            return interaction.reply({
                content: '❌ Du trenger helper tillatelser for å bruke denne kommandoen.',
                ephemeral: true
            });
        }
        
        const target = interaction.options.getUser('bruker');
        const reason = interaction.options.getString('grunn');
        
        // Få target member
        let targetMember;
        try {
            targetMember = await interaction.guild.members.fetch(target.id);
        } catch (error) {
            return interaction.reply({
                content: '❌ Kunne ikke finne denne brukeren i serveren.',
                ephemeral: true
            });
        }
        
        // Sjekk moderation permissions
        if (!permissions.canModerate(interaction.member, targetMember)) {
            return interaction.reply({
                content: '❌ Du kan ikke advare denne brukeren (høyere rolle eller server eier).',
                ephemeral: true
            });
        }
        
        if (target.id === interaction.user.id) {
            return interaction.reply({
                content: '❌ Du kan ikke advare deg selv!',
                ephemeral: true
            });
        }
        
        await interaction.deferReply();
        
        try {
            // Legg til warning i database
            interaction.client.database.addWarning(
                interaction.guild.id,
                target.id,
                interaction.user.id,
                reason
            );
            
            // Få totalt antall warnings (PER SERVER!)
            const warningCount = interaction.client.database.getWarningCount(
                interaction.guild.id,
                target.id
            );
            
            // Send DM til bruker
            const dmEmbed = new EmbedBuilder()
                .setColor(0xf39c12)
                .setTitle('⚠️ Du har mottatt en advarsel')
                .addFields(
                    { name: '🏠 Server', value: interaction.guild.name, inline: true },
                    { name: '👮 Moderator', value: interaction.user.tag, inline: true },
                    { name: '📝 Grunn', value: reason, inline: false },
                    { name: '📊 Totalt antall advarsler', value: warningCount.toString(), inline: true }
                )
                .setThumbnail(interaction.guild.iconURL())
                .setFooter({ text: 'Vær snill å følg server reglene!' })
                .setTimestamp();
            
            try {
                await target.send({ embeds: [dmEmbed] });
            } catch (dmError) {
                console.log(`❌ Kunne ikke sende DM til ${target.tag}`);
            }
            
            // Success embed
            const successEmbed = new EmbedBuilder()
                .setColor(0xf39c12)
                .setTitle('⚠️ Advarsel Gitt')
                .addFields(
                    { name: '👤 Bruker', value: `${target.tag}\n\`${target.id}\``, inline: true },
                    { name: '👮 Moderator', value: `${interaction.user.tag}`, inline: true },
                    { name: '📊 Totalt advarsler', value: warningCount.toString(), inline: true },
                    { name: '📝 Grunn', value: reason, inline: false }
                )
                .setThumbnail(target.displayAvatarURL())
                .setFooter({
                    text: `${interaction.guild.name} Moderasjon`,
                    iconURL: interaction.guild.iconURL()
                })
                .setTimestamp();
            
            // Automatisk handlinger basert på antall warnings
            let autoAction = '';
            if (warningCount >= 5) {
                // 5+ warnings = ban
                try {
                    await targetMember.ban({ reason: 'Automatisk ban - 5 advarsler' });
                    autoAction = '\n🔨 **Automatisk handling:** Bruker bannet (5+ advarsler)';
                } catch (banError) {
                    autoAction = '\n❌ **Kunne ikke auto-banne bruker**';
                }
            } else if (warningCount >= 3) {
                // 3-4 warnings = 1 time timeout
                try {
                    await targetMember.timeout(60 * 60 * 1000, 'Automatisk timeout - 3+ advarsler');
                    autoAction = '\n⏰ **Automatisk handling:** 1 time timeout (3+ advarsler)';
                } catch (timeoutError) {
                    autoAction = '\n❌ **Kunne ikke auto-timeout bruker**';
                }
            }
            
            if (autoAction) {
                successEmbed.addFields({
                    name: '🤖 Automatisk Handling',
                    value: autoAction.substring(1), // Fjern \n
                    inline: false
                });
            }
            
            await interaction.editReply({ embeds: [successEmbed] });
            
            // Logg i mod-log kanal
            const logChannel = interaction.guild.channels.cache.find(channel => 
                channel.name.includes('mod-log') || 
                channel.name.includes('log')
            );
            
            if (logChannel && logChannel.id !== interaction.channel.id) {
                await logChannel.send({ embeds: [successEmbed] });
            }
            
            console.log(`⚠️ ${interaction.user.tag} advarte ${target.tag} (${warningCount} totalt): ${reason}`);
            
        } catch (error) {
            console.error('❌ Warning feil:', error);
            
            await interaction.editReply({
                content: '❌ Kunne ikke gi advarsel til brukeren.',
                ephemeral: true
            });
        }
    },
};

4.3: Lag warnings command (sjekk warnings)

Lag commands/moderation/warnings.js:

javascript
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const permissions = require('../../utils/permissions.js');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('warnings')
        .setDescription('Vis advarsler for en bruker')
        .addUserOption(option =>
            option.setName('bruker')
                .setDescription('Brukeren å sjekke advarsler for')
                .setRequired(true)
        ),
    
    async execute(interaction) {
        if (!permissions.hasStaffRole(interaction.member, 'HELPER')) {
            return interaction.reply({
                content: '❌ Du trenger helper tillatelser for å bruke denne kommandoen.',
                ephemeral: true
            });
        }
        
        const target = interaction.options.getUser('bruker');
        const database = interaction.client.database;
        
        // Hent warnings (PER SERVER!)
        const warnings = database.getWarnings(interaction.guild.id, target.id);
        
        const embed = new EmbedBuilder()
            .setColor(warnings.length > 0 ? 0xf39c12 : 0x2ecc71)
            .setTitle(`⚠️ Advarsler for ${target.tag}`)
            .setThumbnail(target.displayAvatarURL())
            .setFooter({
                text: `${interaction.guild.name} | Totalt ${warnings.length} advarsler`,
                iconURL: interaction.guild.iconURL()
            })
            .setTimestamp();
        
        if (warnings.length === 0) {
            embed.setDescription('✅ Denne brukeren har ingen advarsler!')
                .setColor(0x2ecc71);
        } else {
            let warningList = '';
            
            warnings.slice(0, 10).forEach((warning, index) => { // Vis max 10 warnings
                const date = new Date(warning.created_at).toLocaleDateString('no-NO');
                warningList += `**${index + 1}.** ${warning.reason}\n`;
                warningList += `   📅 ${date} | 👮 <@${warning.moderator_id}>\n\n`;
            });
            
            embed.setDescription(warningList);
            
            if (warnings.length > 10) {
                embed.addFields({
                    name: 'ℹ️ Info',
                    value: `Viser de 10 nyeste av totalt ${warnings.length} advarsler.`,
                    inline: false
                });
            }
            
            // Advarsel om høyt antall
            if (warnings.length >= 3) {
                embed.addFields({
                    name: '🚨 Advarsel',
                    value: `Denne brukeren har ${warnings.length} advarsler og kan trenge ekstra oppmerksomhet.`,
                    inline: false
                });
            }
        }
        
        await interaction.reply({ embeds: [embed] });
    },
};

⏰ Steg 5: Timeout Command

5.1: Lag timeout.js

Lag commands/moderation/timeout.js:

javascript
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const permissions = require('../../utils/permissions.js');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('timeout')
        .setDescription('Timeout en bruker (mute med tidsgrense)')
        .addUserOption(option =>
            option.setName('bruker')
                .setDescription('Brukeren som skal timeoutes')
                .setRequired(true)
        )
        .addIntegerOption(option =>
            option.setName('minutter')
                .setDescription('Hvor lenge timeout skal vare (1-1440 minutter / 24 timer)')
                .setRequired(true)
                .setMinValue(1)
                .setMaxValue(1440)
        )
        .addStringOption(option =>
            option.setName('grunn')
                .setDescription('Grunn til timeout')
                .setRequired(false)
                .setMaxLength(500)
        ),
    
    async execute(interaction) {
        if (!permissions.hasStaffRole(interaction.member, 'MODERATOR')) {
            return interaction.reply({
                content: '❌ Du trenger moderator tillatelser for å bruke denne kommandoen.',
                ephemeral: true
            });
        }
        
        const target = interaction.options.getUser('bruker');
        const minutes = interaction.options.getInteger('minutter');
        const reason = interaction.options.getString('grunn') || 'Ingen grunn oppgitt';
        
        // Få target member
        let targetMember;
        try {
            targetMember = await interaction.guild.members.fetch(target.id);
        } catch (error) {
            return interaction.reply({
                content: '❌ Kunne ikke finne denne brukeren i serveren.',
                ephemeral: true
            });
        }
        
        // Sjekk moderation permissions
        if (!permissions.canModerate(interaction.member, targetMember)) {
            return interaction.reply({
                content: '❌ Du kan ikke timeout denne brukeren (høyere rolle eller server eier).',
                ephemeral: true
            });
        }
        
        // Sjekk bot permissions
        if (!interaction.guild.members.me.permissions.has('ModerateMembers')) {
            return interaction.reply({
                content: '❌ Jeg har ikke tillatelse til å timeout medlemmer.',
                ephemeral: true
            });
        }
        
        if (target.id === interaction.user.id) {
            return interaction.reply({
                content: '❌ Du kan ikke timeout deg selv!',
                ephemeral: true
            });
        }
        
        await interaction.deferReply();
        
        try {
            // Utfør timeout
            const timeoutDuration = minutes * 60 * 1000; // minutter til millisekunder
            await targetMember.timeout(timeoutDuration, reason);
            
            // Logg til database
            interaction.client.database.addModLog(
                interaction.guild.id,
                target.id,
                interaction.user.id,
                'timeout',
                reason,
                minutes
            );
            
            // Format varighet
            const formatDuration = (mins) => {
                if (mins < 60) return `${mins} minutter`;
                const hours = Math.floor(mins / 60);
                const remainingMins = mins % 60;
                return remainingMins > 0 ? 
                    `${hours} timer og ${remainingMins} minutter` : 
                    `${hours} timer`;
            };
            
            // Send DM til bruker
            const dmEmbed = new EmbedBuilder()
                .setColor(0xf39c12)
                .setTitle('⏰ Du har blitt timeout')
                .addFields(
                    { name: '🏠 Server', value: interaction.guild.name, inline: true },
                    { name: '👮 Moderator', value: interaction.user.tag, inline: true },
                    { name: '⏰ Varighet', value: formatDuration(minutes), inline: true },
                    { name: '📝 Grunn', value: reason, inline: false },
                    { 
                        name: 'ℹ️ Hva betyr timeout?', 
                        value: 'Du kan ikke sende meldinger, reagere, eller snakke i voice kanaler i den angitte tiden.', 
                        inline: false 
                    }
                )
                .setThumbnail(interaction.guild.iconURL())
                .setTimestamp();
            
            try {
                await target.send({ embeds: [dmEmbed] });
            } catch (dmError) {
                console.log(`❌ Kunne ikke sende DM til ${target.tag}`);
            }
            
            // Success embed
            const successEmbed = new EmbedBuilder()
                .setColor(0xf39c12)
                .setTitle('⏰ Bruker Timeout')
                .addFields(
                    { name: '👤 Bruker', value: `${target.tag}\n\`${target.id}\``, inline: true },
                    { name: '👮 Moderator', value: `${interaction.user.tag}`, inline: true },
                    { name: '⏰ Varighet', value: formatDuration(minutes), inline: true },
                    { name: '📝 Grunn', value: reason, inline: false },
                    { 
                        name: '📅 Timeout utløper', 
                        value: `<t:${Math.floor((Date.now() + timeoutDuration) / 1000)}:F>`, 
                        inline: false 
                    }
                )
                .setThumbnail(target.displayAvatarURL())
                .setFooter({
                    text: `${interaction.guild.name} Moderasjon`,
                    iconURL: interaction.guild.iconURL()
                })
                .setTimestamp();
            
            await interaction.editReply({ embeds: [successEmbed] });
            
            // Log til server sin log kanal
            await interaction.client.database.logToChannel(
                interaction.client,
                interaction.guild.id,
                `${target.tag} ble timeout av ${interaction.user.tag} (${formatDuration(minutes)}): ${reason}`,
                0xf39c12
            );
            
            console.log(`TIMEOUT: ${interaction.user.tag} timeout ${target.tag} i ${formatDuration(minutes)}: ${reason}`);
            
        } catch (error) {
            console.error('❌ Timeout feil:', error);
            
            await interaction.editReply({
                content: '❌ Kunne ikke timeout brukeren. Sjekk at boten har høyere rolle og riktige tillatelser.',
                ephemeral: true
            });
        }
    },
};

🛡️ Steg 6: Staff Utilities

6.1: Lag modlogs command

Lag commands/moderation/modlogs.js:

javascript
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const permissions = require('../../utils/permissions.js');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('modlogs')
        .setDescription('Vis moderasjon historie for en bruker')
        .addUserOption(option =>
            option.setName('bruker')
                .setDescription('Brukeren å sjekke logs for')
                .setRequired(true)
        ),
    
    async execute(interaction) {
        if (!permissions.hasStaffRole(interaction.member, 'HELPER')) {
            return interaction.reply({
                content: '❌ Du trenger helper tillatelser for å bruke denne kommandoen.',
                ephemeral: true
            });
        }
        
        const target = interaction.options.getUser('bruker');
        const database = interaction.client.database;
        
        // Hent mod logs
        const modLogs = database.getModLogs(interaction.guild.id, target.id, 10);
        const warnings = database.getWarnings(interaction.guild.id, target.id);
        
        const embed = new EmbedBuilder()
            .setColor(0x9b59b6)
            .setTitle(`🛡️ Moderasjon Historie - ${target.tag}`)
            .setThumbnail(target.displayAvatarURL())
            .setFooter({
                text: `${interaction.guild.name} | ${modLogs.length} handlinger, ${warnings.length} advarsler`,
                iconURL: interaction.guild.iconURL()
            })
            .setTimestamp();
        
        if (modLogs.length === 0 && warnings.length === 0) {
            embed.setDescription('✅ Denne brukeren har en ren historikk!')
                .setColor(0x2ecc71);
        } else {
            let logText = '';
            
            // Vis mod logs
            modLogs.forEach((log, index) => {
                const date = new Date(log.created_at).toLocaleDateString('no-NO');
                const actionEmojis = {
                    'kick': '👢',
                    'ban': '🔨', 
                    'tempban': '⏰',
                    'unban': '🔓',
                    'timeout': '⏰',
                    'warn': '⚠️'
                };
                
                const emoji = actionEmojis[log.action] || '📝';
                logText += `${emoji} **${log.action.toUpperCase()}** - ${date}\n`;
                logText += `   👮 <@${log.moderator_id}> | ${log.reason}\n`;
                
                if (log.duration) {
                    logText += `   ⏰ Varighet: ${log.duration} ${log.action === 'timeout' ? 'min' : 'timer'}\n`;
                }
                logText += '\n';
            });
            
            if (logText) {
                embed.addFields({
                    name: '📋 Moderasjon Handlinger',
                    value: logText || 'Ingen handlinger funnet',
                    inline: false
                });
            }
            
            // Sammendrag
            const actionCounts = {};
            modLogs.forEach(log => {
                actionCounts[log.action] = (actionCounts[log.action] || 0) + 1;
            });
            
            let summaryText = '';
            Object.entries(actionCounts).forEach(([action, count]) => {
                const actionEmojis = {
                    'kick': '👢',
                    'ban': '🔨',
                    'tempban': '⏰',
                    'timeout': '⏰',
                    'warn': '⚠️'
                };
                summaryText += `${actionEmojis[action] || '📝'} ${count} ${action}(s)\n`;
            });
            
            summaryText += `⚠️ ${warnings.length} advarsel(er)`;
            
            embed.addFields({
                name: '📊 Sammendrag',
                value: summaryText,
                inline: true
            });
            
            // Risiko vurdering
            let riskLevel = 'Lav';
            let riskColor = 0x2ecc71;
            
            if (warnings.length >= 3 || modLogs.filter(log => log.action === 'ban').length > 0) {
                riskLevel = 'Høy';
                riskColor = 0xe74c3c;
            } else if (warnings.length >= 2 || modLogs.length >= 3) {
                riskLevel = 'Medium';
                riskColor = 0xf39c12;
            }
            
            embed.addFields({
                name: '⚠️ Risiko nivå',
                value: riskLevel,
                inline: true
            }).setColor(riskColor);
        }
        
        await interaction.reply({ embeds: [embed] });
    },
};

6.2: Lag purge command

Lag commands/moderation/purge.js:

javascript
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const permissions = require('../../utils/permissions.js');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('purge')
        .setDescription('Slett flere meldinger på en gang')
        .addIntegerOption(option =>
            option.setName('antall')
                .setDescription('Antall meldinger å slette (1-100)')
                .setRequired(true)
                .setMinValue(1)
                .setMaxValue(100)
        )
        .addUserOption(option =>
            option.setName('bruker')
                .setDescription('Slett kun meldinger fra denne brukeren')
                .setRequired(false)
        ),
    
    async execute(interaction) {
        if (!permissions.hasStaffRole(interaction.member, 'HELPER')) {
            return interaction.reply({
                content: '❌ Du trenger helper tillatelser for å bruke denne kommandoen.',
                ephemeral: true
            });
        }
        
        // Sjekk bot permissions
        if (!interaction.guild.members.me.permissions.has('ManageMessages')) {
            return interaction.reply({
                content: '❌ Jeg har ikke tillatelse til å slette meldinger.',
                ephemeral: true
            });
        }
        
        const amount = interaction.options.getInteger('antall');
        const targetUser = interaction.options.getUser('bruker');
        
        await interaction.deferReply({ ephemeral: true });
        
        try {
            // Hent meldinger
            const messages = await interaction.channel.messages.fetch({ limit: 100 });
            
            let messagesToDelete;
            
            if (targetUser) {
                // Filtrer meldinger fra spesifikk bruker
                messagesToDelete = messages.filter(msg => 
                    msg.author.id === targetUser.id &&
                    Date.now() - msg.createdTimestamp < 14 * 24 * 60 * 60 * 1000 // Under 14 dager
                ).first(amount);
            } else {
                // Slett de nyeste meldingene
                messagesToDelete = messages.filter(msg => 
                    Date.now() - msg.createdTimestamp < 14 * 24 * 60 * 60 * 1000 // Under 14 dager
                ).first(amount);
            }
            
            if (messagesToDelete.length === 0) {
                return interaction.editReply({
                    content: '❌ Fant ingen meldinger å slette (meldinger må være under 14 dager gamle).'
                });
            }
            
            // Slett meldinger
            await interaction.channel.bulkDelete(messagesToDelete, true);
            
            // Success melding
            const embed = new EmbedBuilder()
                .setColor(0x2ecc71)
                .setTitle('🗑️ Meldinger Slettet')
                .addFields(
                    { name: '📊 Antall slettet', value: messagesToDelete.length.toString(), inline: true },
                    { name: '👮 Moderator', value: interaction.user.tag, inline: true },
                    { name: '📍 Kanal', value: interaction.channel.toString(), inline: true }
                )
                .setFooter({
                    text: `${interaction.guild.name} Moderasjon`,
                    iconURL: interaction.guild.iconURL()
                })
                .setTimestamp();
            
            if (targetUser) {
                embed.addFields({
                    name: '👤 Målrettet bruker',
                    value: targetUser.tag,
                    inline: true
                });
            }
            
            await interaction.editReply({ embeds: [embed] });
            
            // Log til server sin log kanal
            await interaction.client.database.logToChannel(
                interaction.client,
                interaction.guild.id,
                `${messagesToDelete.length} meldinger slettet i #${interaction.channel.name} av ${interaction.user.tag}`,
                0x3498db
            );
            
            console.log(`PURGE: ${interaction.user.tag} slettet ${messagesToDelete.length} meldinger i #${interaction.channel.name}`);
            
        } catch (error) {
            console.error('❌ Purge feil:', error);
            
            await interaction.editReply({
                content: '❌ Kunne ikke slette meldinger. Sjekk at meldingene er under 14 dager gamle.',
                ephemeral: true
            });
        }
    },
};

⚙️ Steg 7: Server Settings Commands

7.1: Lag logchannel command

Lag commands/admin/logchannel.js:

javascript
const { SlashCommandBuilder, EmbedBuilder, ChannelType } = require('discord.js');
const permissions = require('../../utils/permissions.js');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('logchannel')
        .setDescription('Sett eller vis log kanal for server')
        .addSubcommand(subcommand =>
            subcommand
                .setName('set')
                .setDescription('Sett log kanal')
                .addChannelOption(option =>
                    option.setName('kanal')
                        .setDescription('Kanalen som skal brukes til logging')
                        .setRequired(true)
                        .addChannelTypes(ChannelType.GuildText)
                )
        )
        .addSubcommand(subcommand =>
            subcommand
                .setName('show')
                .setDescription('Vis nåværende log kanal')
        )
        .addSubcommand(subcommand =>
            subcommand
                .setName('remove')
                .setDescription('Fjern log kanal (deaktiver logging)')
        ),
    
    async execute(interaction) {
        if (!permissions.hasStaffRole(interaction.member, 'ADMIN')) {
            return interaction.reply({
                content: 'Du trenger administrator tillatelser for å bruke denne kommandoen.',
                ephemeral: true
            });
        }
        
        const subcommand = interaction.options.getSubcommand();
        const database = interaction.client.database;
        
        switch (subcommand) {
            case 'set':
                const channel = interaction.options.getChannel('kanal');
                
                // Sjekk at bot kan skrive i kanalen
                if (!channel.permissionsFor(interaction.guild.members.me).has('SendMessages')) {
                    return interaction.reply({
                        content: 'Jeg har ikke tillatelse til å skrive i den kanalen.',
                        ephemeral: true
                    });
                }
                
                await database.updateLogChannel(interaction.guild.id, channel.id);
                
                const setEmbed = new EmbedBuilder()
                    .setColor(0x2ecc71)
                    .setTitle('Log Kanal Oppdatert')
                    .setDescription(`Log kanal er nå satt til ${channel}`)
                    .addFields(
                        { name: 'Kanal', value: channel.toString(), inline: true },
                        { name: 'ID', value: channel.id, inline: true }
                    )
                    .setTimestamp();
                
                await interaction.reply({ embeds: [setEmbed] });
                
                // Send test melding til log kanal
                await database.logToChannel(
                    interaction.client,
                    interaction.guild.id,
                    `Log kanal aktivert av ${interaction.user.tag}`,
                    0x2ecc71
                );
                break;
                
            case 'show':
                const currentChannel = await database.getLogChannel(interaction.guild.id);
                
                const showEmbed = new EmbedBuilder()
                    .setTitle('Nåværende Log Kanal')
                    .setColor(currentChannel ? 0x3498db : 0x95a5a6)
                    .setTimestamp();
                
                if (currentChannel) {
                    const channel = interaction.guild.channels.cache.get(currentChannel);
                    showEmbed.setDescription(channel ? `Log kanal: ${channel}` : 'Log kanal finnes ikke lenger')
                        .addFields(
                            { name: 'Kanal ID', value: currentChannel, inline: true },
                            { name: 'Status', value: channel ? 'Aktiv' : 'Ugyldig', inline: true }
                        );
                } else {
                    showEmbed.setDescription('Ingen log kanal er satt')
                        .addFields({
                            name: 'Sett log kanal',
                            value: 'Bruk `/logchannel set #kanal` for å aktivere logging',
                            inline: false
                        });
                }
                
                await interaction.reply({ embeds: [showEmbed] });
                break;
                
            case 'remove':
                await database.updateLogChannel(interaction.guild.id, null);
                
                const removeEmbed = new EmbedBuilder()
                    .setColor(0xe74c3c)
                    .setTitle('Log Kanal Fjernet')
                    .setDescription('Logging til kanal er nå deaktivert')
                    .addFields({
                        name: 'Status',
                        value: 'Alle moderation handlinger vil kun logges til konsoll',
                        inline: false
                    })
                    .setTimestamp();
                
                await interaction.reply({ embeds: [removeEmbed] });
                break;
        }
    },
};

🧪 Steg 8: Testing Staff System

7.1: Registrer alle nye commands

bash
node deploy-commands.js

7.2: Start boten

bash
node index.js

7.3: Test alle staff commands

Test permissions først:

  • Prøv commands uten riktige roller - skal få feilmelding
  • Gi deg selv Moderate Members eller Administrator rolle

Test commands:

  • /warn @bruker grunn:Testing warning system
  • /warnings @bruker - Se advarsler
  • /kick @testbruker grunn:Testing kick
  • /timeout @bruker minutter:5 grunn:Testing timeout
  • /ban @bruker grunn:Testing ban varighet:1
  • /modlogs @bruker - Se all mod historie
  • /purge antall:10 - Slett 10 meldinger

7.4: Sjekk logging

  1. Database - Sjekk at handlinger logges
  2. Console - Se logging output
  3. Log kanal - Bruk /logchannel set #kanal for å aktivere logging

✅ Oppsummering

Staff system vi har bygget:

  • 🛡️ 3-nivå rolle hierarki (Admin/Moderator/Helper)
  • 👮 Permission checking - Rollbasert tilgangskontroll
  • ⚠️ Warning system - Database lagret advarsler
  • 🔨 Kick/Ban commands - Med logging og DM notifications
  • Timeout system - Tidsbasert mute
  • 📊 Moderation logs - Full historie av handlinger
  • 🗑️ Purge system - Bulk sletting av meldinger
  • 💾 Database integration - Alt lagres permanent

Automatiske features:

  • Auto-punishment - 3 warnings = timeout, 5 = ban
  • DM notifications - Brukere får beskjed om handlinger
  • Comprehensive logging - Alt spores i database og kanaler
  • Permission validation - Kan ikke moderate høyere roller

🚀 Neste Steg

I del 8 skal vi lage Avanserte Features! Vi lærer:

  • Avansert XP system med belønninger
  • Auto-role system basert på aktivitet
  • Custom embed builder
  • Reaction role system
  • Server backup og restore
  • Advanced logging med webhooks

Klar for å lage superkrefter til boten?