Komplett Discord Bot Guide
Del 7: Staff System med Moderasjon
📋 Innholdsfortegnelse
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?
- Staff rolle hierarki - Hvem kan gjøre hva?
- Permission system - Rollbasert tilgangskontroll
- Kick/Ban commands - Med logging til database
- Warning system - Gi advarsler til brukere
- Timeout commands - Midlertidig mute brukere
- Staff utilities - Nyttige moderator verktøy
- 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:
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:
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:
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:
// 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:
// 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:
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:
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:
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:
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:
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:
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
node deploy-commands.js
7.2: Start boten
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
- Database - Sjekk at handlinger logges
- Console - Se logging output
- Log kanal - Bruk
/logchannel set #kanalfor å 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?