Komplett Discord Bot Guide
Del 8: Avanserte Features
📋 Innholdsfortegnelse
Del 8: Avanserte Features
Nå skal vi lage de virkelig kule funksjonene! Dette er hvor boten din går fra "grunnleggende" til "imponerende" med avanserte systemer som XP belønninger, auto-roles og mye mer.
Hva lærer vi i denne delen?
- Avansert XP system - Belønninger, multipliers, leaderboard improvements
- Auto-role system - Gi roller basert på aktivitet og levels
- Custom embed builder - La brukere lage fancy meldinger
- Reaction role system - Få roller ved å reagere på meldinger
- Voice activity tracking - XP for tid i voice kanaler
- Economy system - Virtuell økonomi med coins
- Server settings - Konfigurerbare bot innstillinger per server
Steg 1: Avansert XP System
1.1: Oppdater database for XP features
Legg til i database/database.js - i init():
// XP multipliers og belønninger
this.db.exec(`
CREATE TABLE IF NOT EXISTS xp_rewards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
guild_id TEXT NOT NULL,
level INTEGER NOT NULL,
reward_type TEXT NOT NULL, -- 'role', 'coins', 'title'
reward_value TEXT NOT NULL, -- role_id, coin_amount, eller title_name
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(guild_id, level)
)
`);
// Economy system
this.db.exec(`
CREATE TABLE IF NOT EXISTS user_economy (
guild_id TEXT NOT NULL,
user_id TEXT NOT NULL,
coins INTEGER DEFAULT 0,
daily_streak INTEGER DEFAULT 0,
last_daily DATETIME,
total_earned INTEGER DEFAULT 0,
total_spent INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (guild_id, user_id)
)
`);
// Voice activity tracking
this.db.exec(`
CREATE TABLE IF NOT EXISTS voice_sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
guild_id TEXT NOT NULL,
user_id TEXT NOT NULL,
channel_id TEXT NOT NULL,
joined_at DATETIME DEFAULT CURRENT_TIMESTAMP,
left_at DATETIME,
duration_minutes INTEGER
)
`);
Legg til nye database funksjoner:
// XP Reward functions
addXPReward(guildId, level, rewardType, rewardValue) {
const stmt = this.db.prepare(`
INSERT OR REPLACE INTO xp_rewards
(guild_id, level, reward_type, reward_value)
VALUES (?, ?, ?, ?)
`);
return stmt.run(guildId, level, rewardType, rewardValue);
}
getXPReward(guildId, level) {
const stmt = this.db.prepare(`
SELECT * FROM xp_rewards
WHERE guild_id = ? AND level = ?
`);
return stmt.get(guildId, level);
}
getAllXPRewards(guildId) {
const stmt = this.db.prepare(`
SELECT * FROM xp_rewards
WHERE guild_id = ?
ORDER BY level ASC
`);
return stmt.all(guildId);
}
// Economy functions
getUserEconomy(guildId, userId) {
const stmt = this.db.prepare(`
SELECT * FROM user_economy
WHERE guild_id = ? AND user_id = ?
`);
return stmt.get(guildId, userId);
}
createUserEconomy(guildId, userId) {
const stmt = this.db.prepare(`
INSERT OR IGNORE INTO user_economy (guild_id, user_id)
VALUES (?, ?)
`);
return stmt.run(guildId, userId);
}
addCoins(guildId, userId, amount) {
this.createUserEconomy(guildId, userId);
const stmt = this.db.prepare(`
UPDATE user_economy
SET coins = coins + ?,
total_earned = total_earned + ?
WHERE guild_id = ? AND user_id = ?
`);
return stmt.run(amount, amount, guildId, userId);
}
removeCoins(guildId, userId, amount) {
this.createUserEconomy(guildId, userId);
const stmt = this.db.prepare(`
UPDATE user_economy
SET coins = coins - ?,
total_spent = total_spent + ?
WHERE guild_id = ? AND user_id = ? AND coins >= ?
`);
return stmt.run(amount, amount, guildId, userId, amount);
}
// Voice tracking functions
startVoiceSession(guildId, userId, channelId) {
const stmt = this.db.prepare(`
INSERT INTO voice_sessions (guild_id, user_id, channel_id)
VALUES (?, ?, ?)
`);
return stmt.run(guildId, userId, channelId);
}
endVoiceSession(guildId, userId) {
const stmt = this.db.prepare(`
UPDATE voice_sessions
SET left_at = CURRENT_TIMESTAMP,
duration_minutes = (
strftime('%s', CURRENT_TIMESTAMP) - strftime('%s', joined_at)
) / 60
WHERE guild_id = ? AND user_id = ? AND left_at IS NULL
`);
return stmt.run(guildId, userId);
}
getVoiceTime(guildId, userId, days = 30) {
const stmt = this.db.prepare(`
SELECT COALESCE(SUM(duration_minutes), 0) as total_minutes
FROM voice_sessions
WHERE guild_id = ? AND user_id = ?
AND datetime(joined_at) >= datetime('now', '-${days} days')
`);
return stmt.get(guildId, userId).total_minutes;
}
1.2: Oppdater XP system med belønninger
Rediger events/message/messageCreate.js - erstatt XP delen:
// XP System med belønninger
const userData = database.getUser(message.author.id);
const oldLevel = userData ? userData.level : 0;
// XP multiplier basert på tid på dagen og kanal type
let xpMultiplier = 1;
// Bonus XP på kvelden (18-23)
const hour = new Date().getHours();
if (hour >= 18 && hour <= 23) xpMultiplier += 0.5;
// Bonus XP i visse kanaler
const bonusChannels = ['general', 'chat', 'discussion'];
if (bonusChannels.some(name => message.channel.name.includes(name))) {
xpMultiplier += 0.3;
}
// Random XP med multiplier
const baseXP = Math.floor(Math.random() * 10) + 5; // 5-14 base XP
const finalXP = Math.floor(baseXP * xpMultiplier);
database.addXP(message.author.id, finalXP);
// Gi coins for aktivitet
database.addCoins(message.guild.id, message.author.id, Math.floor(finalXP / 2));
// Sjekk for level up og belønninger
const newUserData = database.getUser(message.author.id);
const newLevel = newUserData.level;
if (newLevel > oldLevel) {
message.react('🎉');
// Sjekk for belønninger
const reward = database.getXPReward(message.guild.id, newLevel);
let rewardText = '';
if (reward) {
if (reward.reward_type === 'role') {
const role = message.guild.roles.cache.get(reward.reward_value);
if (role) {
try {
const member = await message.guild.members.fetch(message.author.id);
await member.roles.add(role);
rewardText = `\n🎁 **Belønning:** Du fikk rollen ${role.name}!`;
} catch (roleError) {
console.error('❌ Kunne ikke gi rolle belønning:', roleError);
}
}
} else if (reward.reward_type === 'coins') {
const coinAmount = parseInt(reward.reward_value);
database.addCoins(message.guild.id, message.author.id, coinAmount);
rewardText = `\n🪙 **Belønning:** ${coinAmount} coins!`;
}
}
await message.reply(`🎉 Gratulerer ${message.author}! Du nådde level ${newLevel}! ${rewardText}`);
console.log(`🎉 ${message.author.tag} nådde level ${newLevel}`);
}
🗣️ Steg 2: Voice Activity Tracking
2.1: Voice state events
Lag events/voice/voiceStateUpdate.js:
module.exports = {
name: 'voiceStateUpdate',
async execute(oldState, newState) {
const database = newState.client.database;
const guildId = newState.guild.id;
const userId = newState.member.user.id;
// Bruker jointet voice kanal
if (!oldState.channelId && newState.channelId) {
console.log(`🎤 ${newState.member.user.tag} jointet voice kanal: ${newState.channel.name}`);
database.startVoiceSession(guildId, userId, newState.channelId);
// Opprett bruker hvis ikke finnes
database.createOrUpdateUser(
userId,
newState.member.user.username,
newState.member.user.discriminator
);
}
// Bruker forlot voice kanal
if (oldState.channelId && !newState.channelId) {
console.log(`🎤 ${newState.member.user.tag} forlot voice kanal`);
database.endVoiceSession(guildId, userId);
// Gi XP for voice aktivitet (1 XP per minutt, minimum 1 minutt)
const voiceTime = database.getVoiceTime(guildId, userId, 1); // Siste dag
if (voiceTime >= 1) {
const voiceXP = Math.floor(voiceTime); // 1 XP per minutt i voice
database.addXP(userId, voiceXP);
// Gi coins også
database.addCoins(guildId, userId, Math.floor(voiceXP / 2));
console.log(`🎤 Ga ${newState.member.user.tag} ${voiceXP} XP for ${Math.floor(voiceTime)} minutter i voice`);
}
}
// Bruker byttet voice kanal
if (oldState.channelId && newState.channelId && oldState.channelId !== newState.channelId) {
console.log(`🎤 ${newState.member.user.tag} byttet fra ${oldState.channel.name} til ${newState.channel.name}`);
// End current session og start ny
database.endVoiceSession(guildId, userId);
database.startVoiceSession(guildId, userId, newState.channelId);
}
},
};
🎭 Steg 3: Reaction Role System
3.1: Reaction role database
Legg til i database/database.js:
// Reaction roles
this.db.exec(`
CREATE TABLE IF NOT EXISTS reaction_roles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
guild_id TEXT NOT NULL,
message_id TEXT NOT NULL,
emoji TEXT NOT NULL,
role_id TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(message_id, emoji)
)
`);
// Reaction role functions
addReactionRole(guildId, messageId, emoji, roleId) {
const stmt = this.db.prepare(`
INSERT OR REPLACE INTO reaction_roles
(guild_id, message_id, emoji, role_id)
VALUES (?, ?, ?, ?)
`);
return stmt.run(guildId, messageId, emoji, roleId);
}
getReactionRole(messageId, emoji) {
const stmt = this.db.prepare(`
SELECT * FROM reaction_roles
WHERE message_id = ? AND emoji = ?
`);
return stmt.get(messageId, emoji);
}
getReactionRoles(messageId) {
const stmt = this.db.prepare(`
SELECT * FROM reaction_roles
WHERE message_id = ?
`);
return stmt.all(messageId);
}
removeReactionRole(messageId, emoji) {
const stmt = this.db.prepare(`
DELETE FROM reaction_roles
WHERE message_id = ? AND emoji = ?
`);
return stmt.run(messageId, emoji);
}
3.2: Reaction role commands
Lag commands/admin/reactionrole.js:
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const permissions = require('../../utils/permissions.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('reactionrole')
.setDescription('Sett opp reaction roles')
.addSubcommand(subcommand =>
subcommand.setName('add')
.setDescription('Legg til reaction role')
.addStringOption(option =>
option.setName('message_id')
.setDescription('ID på meldingen som skal ha reaction role')
.setRequired(true)
)
.addStringOption(option =>
option.setName('emoji')
.setDescription('Emoji som gir rolle (f.eks. 🎮 eller :custom_emoji:)')
.setRequired(true)
)
.addRoleOption(option =>
option.setName('rolle')
.setDescription('Rollen som skal gis')
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand.setName('remove')
.setDescription('Fjern reaction role')
.addStringOption(option =>
option.setName('message_id')
.setDescription('ID på meldingen')
.setRequired(true)
)
.addStringOption(option =>
option.setName('emoji')
.setDescription('Emoji som skal fjernes')
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand.setName('list')
.setDescription('Vis alle reaction roles i denne serveren')
),
async execute(interaction) {
if (!permissions.hasStaffRole(interaction.member, 'ADMIN')) {
return interaction.reply({
content: '❌ Du trenger administrator tillatelser for å bruke denne kommandoen.',
ephemeral: true
});
}
const database = interaction.client.database;
const subcommand = interaction.options.getSubcommand();
if (subcommand === 'add') {
const messageId = interaction.options.getString('message_id');
const emoji = interaction.options.getString('emoji');
const role = interaction.options.getRole('rolle');
// Prøv å finne meldingen
let message;
try {
message = await interaction.channel.messages.fetch(messageId);
} catch (error) {
return interaction.reply({
content: '❌ Kunne ikke finne melding med den ID-en i denne kanalen.',
ephemeral: true
});
}
// Legg til reaction role i database
database.addReactionRole(interaction.guild.id, messageId, emoji, role.id);
// Legg til reaction på meldingen
try {
await message.react(emoji);
} catch (error) {
return interaction.reply({
content: '❌ Kunne ikke reagere med den emoji. Sjekk at emoji er gyldig.',
ephemeral: true
});
}
const embed = new EmbedBuilder()
.setColor(0x2ecc71)
.setTitle('✅ Reaction Role Lagt Til')
.addFields(
{ name: '📝 Melding ID', value: messageId, inline: true },
{ name: '😀 Emoji', value: emoji, inline: true },
{ name: '🎭 Rolle', value: role.toString(), inline: true }
)
.setTimestamp();
await interaction.reply({ embeds: [embed], ephemeral: true });
} else if (subcommand === 'remove') {
const messageId = interaction.options.getString('message_id');
const emoji = interaction.options.getString('emoji');
const removed = database.removeReactionRole(messageId, emoji);
if (removed.changes > 0) {
await interaction.reply({
content: `✅ Reaction role for ${emoji} på melding ${messageId} er fjernet.`,
ephemeral: true
});
} else {
await interaction.reply({
content: '❌ Fant ikke den reaction role kombinasjonen.',
ephemeral: true
});
}
} else if (subcommand === 'list') {
// Implementer liste funksjonalitet
await interaction.reply({
content: '📋 Reaction roles liste funksjon kommer snart!',
ephemeral: true
});
}
},
};
3.3: Reaction role event handler
Oppdater events/message/messageReactionAdd.js:
module.exports = {
name: 'messageReactionAdd',
async execute(reaction, user) {
if (user.bot) return;
// Handle partial reactions
if (reaction.partial) {
try {
await reaction.fetch();
} catch (error) {
console.error('Could not fetch reaction:', error);
return;
}
}
const database = reaction.client.database;
const emojiName = reaction.emoji.name;
// Sjekk om dette er en reaction role
const reactionRole = database.getReactionRole(reaction.message.id, emojiName);
if (reactionRole) {
const guild = reaction.message.guild;
const member = guild.members.cache.get(user.id);
const role = guild.roles.cache.get(reactionRole.role_id);
if (role && member && !member.roles.cache.has(role.id)) {
try {
await member.roles.add(role);
console.log(`✅ Ga ${user.tag} rollen ${role.name} via reaction role`);
// Send DM bekrefelse
try {
const dmEmbed = new EmbedBuilder()
.setColor(0x2ecc71)
.setTitle('🎭 Rolle Gitt!')
.setDescription(`Du har fått rollen **${role.name}** i ${guild.name}!`)
.setThumbnail(guild.iconURL())
.setTimestamp();
await user.send({ embeds: [dmEmbed] });
} catch (dmError) {
// Ignore DM errors
}
} catch (error) {
console.error('❌ Kunne ikke gi rolle via reaction:', error);
}
}
}
},
};
// Lag også messageReactionRemove.js
Lag events/message/messageReactionRemove.js:
module.exports = {
name: 'messageReactionRemove',
async execute(reaction, user) {
if (user.bot) return;
if (reaction.partial) {
try {
await reaction.fetch();
} catch (error) {
console.error('Could not fetch reaction:', error);
return;
}
}
const database = reaction.client.database;
const emojiName = reaction.emoji.name;
// Sjekk om dette er en reaction role
const reactionRole = database.getReactionRole(reaction.message.id, emojiName);
if (reactionRole) {
const guild = reaction.message.guild;
const member = guild.members.cache.get(user.id);
const role = guild.roles.cache.get(reactionRole.role_id);
if (role && member && member.roles.cache.has(role.id)) {
try {
await member.roles.remove(role);
console.log(`➖ Fjernet rollen ${role.name} fra ${user.tag} via reaction role`);
} catch (error) {
console.error('❌ Kunne ikke fjerne rolle via reaction:', error);
}
}
}
},
};
💰 Steg 4: Economy System
4.1: Balance command
Lag commands/economy/balance.js:
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('balance')
.setDescription('Sjekk din eller andres økonomi')
.addUserOption(option =>
option.setName('bruker')
.setDescription('Brukeren å sjekke (valgfritt)')
.setRequired(false)
),
async execute(interaction) {
const target = interaction.options.getUser('bruker') || interaction.user;
const database = interaction.client.database;
// Opprett økonomi hvis ikke finnes
database.createUserEconomy(interaction.guild.id, target.id);
// Hent økonomi data
const economy = database.getUserEconomy(interaction.guild.id, target.id);
const userData = database.getUser(target.id);
const embed = new EmbedBuilder()
.setColor(0xf1c40f)
.setTitle(`💰 ${target.username} sin økonomi`)
.setThumbnail(target.displayAvatarURL())
.addFields(
{
name: '🪙 Nåværende balanse',
value: `${economy.coins.toLocaleString()} coins`,
inline: true
},
{
name: '📈 Totalt tjent',
value: `${economy.total_earned.toLocaleString()} coins`,
inline: true
},
{
name: '💸 Totalt brukt',
value: `${economy.total_spent.toLocaleString()} coins`,
inline: true
},
{
name: '🔥 Daily streak',
value: `${economy.daily_streak} dager`,
inline: true
},
{
name: '🏆 Level',
value: userData ? userData.level.toString() : '0',
inline: true
},
{
name: '⭐ Total XP',
value: userData ? userData.xp.toLocaleString() : '0',
inline: true
}
)
.setFooter({
text: 'Bruk /daily for å få daglige coins!',
iconURL: interaction.guild.iconURL()
})
.setTimestamp();
await interaction.reply({ embeds: [embed] });
},
};
4.2: Daily rewards
Lag commands/economy/daily.js:
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('daily')
.setDescription('Få dine daglige coins!'),
async execute(interaction) {
const database = interaction.client.database;
const guildId = interaction.guild.id;
const userId = interaction.user.id;
// Opprett økonomi hvis ikke finnes
database.createUserEconomy(guildId, userId);
const economy = database.getUserEconomy(guildId, userId);
const lastDaily = economy.last_daily ? new Date(economy.last_daily) : null;
const now = new Date();
// Sjekk om bruker kan få daily
if (lastDaily) {
const timeSinceDaily = now - lastDaily;
const hoursLeft = 24 - (timeSinceDaily / (1000 * 60 * 60));
if (hoursLeft > 0) {
const embed = new EmbedBuilder()
.setColor(0xe74c3c)
.setTitle('⏰ Daily allerede hentet!')
.setDescription(`Du kan få din neste daily om **${Math.ceil(hoursLeft)} timer**.`)
.setFooter({ text: 'Kom tilbake senere!' })
.setTimestamp();
return interaction.reply({ embeds: [embed], ephemeral: true });
}
}
// Kalkuler streak bonus
let streak = economy.daily_streak;
const yesterday = new Date(now);
yesterday.setDate(yesterday.getDate() - 1);
if (lastDaily && lastDaily.toDateString() === yesterday.toDateString()) {
streak += 1; // Fortsett streak
} else if (!lastDaily || lastDaily.toDateString() !== yesterday.toDateString()) {
streak = 1; // Start ny streak
}
// Kalkuler coins (base 100, +10 per streak dag, max 500)
const baseCoins = 100;
const streakBonus = Math.min(streak * 10, 400);
const totalCoins = baseCoins + streakBonus;
// Oppdater database
const stmt = database.db.prepare(`
UPDATE user_economy
SET coins = coins + ?,
daily_streak = ?,
last_daily = CURRENT_TIMESTAMP,
total_earned = total_earned + ?
WHERE guild_id = ? AND user_id = ?
`);
stmt.run(totalCoins, streak, totalCoins, guildId, userId);
const embed = new EmbedBuilder()
.setColor(0x2ecc71)
.setTitle('🎁 Daily Coins Hentet!')
.addFields(
{
name: '🪙 Coins mottatt',
value: `${totalCoins.toLocaleString()}`,
inline: true
},
{
name: '🔥 Streak',
value: `${streak} dager`,
inline: true
},
{
name: '💰 Ny balanse',
value: `${(economy.coins + totalCoins).toLocaleString()}`,
inline: true
}
)
.setDescription(`${baseCoins} base coins + ${streakBonus} streak bonus!`)
.setThumbnail(interaction.user.displayAvatarURL())
.setFooter({
text: 'Kom tilbake i morgen for mer coins!',
iconURL: interaction.guild.iconURL()
})
.setTimestamp();
await interaction.reply({ embeds: [embed] });
console.log(`💰 ${interaction.user.tag} hentet daily: ${totalCoins} coins (streak: ${streak})`);
},
};
🎨 Steg 5: Custom Embed Builder
5.1: Embed builder command
Lag commands/utility/embed.js:
const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
const permissions = require('../../utils/permissions.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('embed')
.setDescription('Lag custom embed meldinger')
.addStringOption(option =>
option.setName('title')
.setDescription('Embed tittel')
.setRequired(true)
.setMaxLength(256)
)
.addStringOption(option =>
option.setName('description')
.setDescription('Embed beskrivelse')
.setRequired(false)
.setMaxLength(2048)
)
.addStringOption(option =>
option.setName('color')
.setDescription('Embed farge (hex kode, f.eks. #ff0000)')
.setRequired(false)
)
.addStringOption(option =>
option.setName('image')
.setDescription('Bilde URL')
.setRequired(false)
)
.addStringOption(option =>
option.setName('thumbnail')
.setDescription('Thumbnail URL')
.setRequired(false)
)
.addBooleanOption(option =>
option.setName('timestamp')
.setDescription('Vis timestamp?')
.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
});
}
const title = interaction.options.getString('title');
const description = interaction.options.getString('description') || '';
const colorHex = interaction.options.getString('color') || '#3498db';
const image = interaction.options.getString('image');
const thumbnail = interaction.options.getString('thumbnail');
const showTimestamp = interaction.options.getBoolean('timestamp') || false;
// Parse color
let color = 0x3498db;
try {
color = parseInt(colorHex.replace('#', ''), 16);
} catch (error) {
color = 0x3498db; // Default blue
}
const embed = new EmbedBuilder()
.setColor(color)
.setTitle(title)
.setDescription(description)
.setFooter({
text: `Laget av ${interaction.user.tag}`,
iconURL: interaction.user.displayAvatarURL()
});
if (image) {
try {
embed.setImage(image);
} catch (error) {
// Invalid image URL - ignore
}
}
if (thumbnail) {
try {
embed.setThumbnail(thumbnail);
} catch (error) {
// Invalid thumbnail URL - ignore
}
}
if (showTimestamp) {
embed.setTimestamp();
}
// Buttons for additional options
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('embed_add_field')
.setLabel('Legg til felt')
.setStyle(ButtonStyle.Primary)
.setEmoji('📝'),
new ButtonBuilder()
.setCustomId('embed_send')
.setLabel('Send embed')
.setStyle(ButtonStyle.Success)
.setEmoji('✅'),
new ButtonBuilder()
.setCustomId('embed_cancel')
.setLabel('Avbryt')
.setStyle(ButtonStyle.Danger)
.setEmoji('❌')
);
await interaction.reply({
content: '📋 **Embed Preview:**',
embeds: [embed],
components: [row],
ephemeral: true
});
// Store embed data for buttons (you'd want to use a better storage method in production)
if (!interaction.client.embedBuilder) {
interaction.client.embedBuilder = new Map();
}
interaction.client.embedBuilder.set(interaction.user.id, {
embed: embed.toJSON(),
originalInteraction: interaction
});
},
};
5.2: Embed button interactions
Lag events/interaction/buttonInteraction.js:
const { EmbedBuilder, ModalBuilder, TextInputBuilder, ActionRowBuilder, TextInputStyle } = require('discord.js');
module.exports = {
name: 'interactionCreate',
async execute(interaction) {
if (!interaction.isButton()) return;
const { customId, user } = interaction;
if (customId.startsWith('embed_')) {
const embedData = interaction.client.embedBuilder?.get(user.id);
if (!embedData) {
return interaction.reply({
content: '❌ Embed data ikke funnet. Prøv å bruke `/embed` kommandoen igjen.',
ephemeral: true
});
}
if (customId === 'embed_add_field') {
// Show modal for adding field
const modal = new ModalBuilder()
.setCustomId('embed_field_modal')
.setTitle('Legg til felt i embed');
const nameInput = new TextInputBuilder()
.setCustomId('field_name')
.setLabel('Felt navn')
.setStyle(TextInputStyle.Short)
.setMaxLength(256)
.setRequired(true);
const valueInput = new TextInputBuilder()
.setCustomId('field_value')
.setLabel('Felt verdi')
.setStyle(TextInputStyle.Paragraph)
.setMaxLength(1024)
.setRequired(true);
const inlineInput = new TextInputBuilder()
.setCustomId('field_inline')
.setLabel('Inline? (true/false)')
.setStyle(TextInputStyle.Short)
.setValue('false')
.setRequired(false);
modal.addComponents(
new ActionRowBuilder().addComponents(nameInput),
new ActionRowBuilder().addComponents(valueInput),
new ActionRowBuilder().addComponents(inlineInput)
);
await interaction.showModal(modal);
} else if (customId === 'embed_send') {
// Send the embed to the channel
const embed = new EmbedBuilder(embedData.embed);
await interaction.channel.send({ embeds: [embed] });
await interaction.reply({
content: '✅ Embed sendt!',
ephemeral: true
});
// Clean up
interaction.client.embedBuilder.delete(user.id);
} else if (customId === 'embed_cancel') {
// Cancel embed creation
await interaction.reply({
content: '❌ Embed opprettelse avbrutt.',
ephemeral: true
});
// Clean up
interaction.client.embedBuilder.delete(user.id);
}
}
}
};
✅ Testing Avanserte Features
6.1: Registrer nye commands
node deploy-commands.js
6.2: Start boten og test
node index.js
6.3: Test alle nye features
XP System:
- Send meldinger og se forbedret XP system
- Join voice kanaler og få voice XP
Economy System:
/balance- Sjekk din økonomi/daily- Få daglige coins- Test streak system over flere dager
Reaction Roles:
/reactionrole add- Sett opp reaction role- React på meldinger og få roller automatisk
Custom Embeds:
/embed- Lag fancy embeds med buttons og interaktivitet
📊 Oppsummering
Avanserte features vi har lagt til:
- 💎 Forbedret XP system - Multipliers, belønninger, voice tracking
- 💰 Economy system - Coins, daily rewards, streak system
- 🎭 Reaction roles - Automatisk rolle tildeling
- 🎨 Custom embed builder - Lag profesjonelle meldinger
- 🗣️ Voice activity - XP for tid i voice kanaler
- 🏆 Level rewards - Automatiske belønninger ved level ups
Database utvidelser:
xp_rewards- Level belønningeruser_economy- Virtual økonomivoice_sessions- Voice aktivitet trackingreaction_roles- Reaction rolle system
🚀 Neste Steg
I del 9 skal vi fokusere på Testing og Debugging! Vi lærer:
- Comprehensive testing av alle bot features
- Error handling og logging improvements
- Performance optimization
- User testing og feedback implementation
- Bug fixing og stability improvements
Klar for å gjøre boten bulletproof?