Merge pull request #3 from Fretuks/main

Implement more commands
This commit is contained in:
2026-01-26 14:11:33 +01:00
committed by GitHub
18 changed files with 266 additions and 48 deletions
+13 -13
View File
@@ -6,16 +6,16 @@ Im going to make a To-do List for the features i want to implement for my Bot.
(I will try to make these tools optional, because alot of bots already feature them) (I will try to make these tools optional, because alot of bots already feature them)
1. **Basic Commands** 1. **Basic Commands**
- [x] Add a `!help` command to display a list of available commands. - [x] Add a `/help` command to display a list of available commands.
- [x] Implement a `!ping` command to check bot responsiveness. - [x] Implement a `/ping` command to check bot responsiveness.
- [ **Only Text missing** ] Add a `!about` command to describe the bot's purpose. - [x] Add an `/about` command to describe the bot's purpose.
2. **Role Management** 2. **Role Management**
- [ ] Set up a self-assignable role system (e.g., using reaction roles). - [ ] Set up a self-assignable role system (e.g., using reaction roles).
- [ ] Allow admins to assign/remove roles with commands. - [ ] Allow admins to assign/remove roles with commands.
3. **Moderation Tools** 3. **Moderation Tools**
- [ ] Add commands to mute, x kick, and x temp_ban users. - [x] Add commands to mute, kick, and temp_ban users.
- [ ] Create an auto-moderation feature to delete spam or inappropriate content. - [ ] Create an auto-moderation feature to delete spam or inappropriate content.
- [ ] Implement a warning system with a strike counter. - [ ] Implement a warning system with a strike counter.
@@ -23,23 +23,23 @@ Im going to make a To-do List for the features i want to implement for my Bot.
#### **Unique Features** #### **Unique Features**
1. **Currency System** 1. **Currency System**
- [ ] Design a virtual currency system with basic commands: - [x] Design a virtual currency system with basic commands:
- [ ] `!balance` to check currency balance. - [x] `/balance` to check currency balance.
- [ ] `!earn` to earn daily or periodic rewards. - [x] `/earn` to earn daily or periodic rewards.
- [ ] `!transfer @user [amount]` to send currency to another user. - [x] `/transfer @user [amount]` to send currency to another user.
- [ ] Link the currency system to role management: - [ ] Link the currency system to role management:
- [ ] Allow users to purchase roles using the currency with a command (e.g., `!buyrole [role name]`). - [ ] Allow users to purchase roles using the currency with a command (e.g., `!buyrole [role name]`).
2. **Datetime Converter** 2. **Datetime Converter**
(Still in Progress, maybe i'll add visual Menu) (Still in Progress, maybe i'll add visual Menu)
- [x] Create a command to convert timezones: - [x] Create a command to convert timezones:
- [x] `!convert [time] [timezone] to [timezone]` to handle conversions. - [x] `/timestamp [date]` to generate Discord timestamps.
- [x] Support both 12-hour and 24-hour formats. - [x] Support multiple Discord timestamp formats.
- [ ] Include a command to display server-specific or user-specific timezones. - [ ] Include a command to display server-specific or user-specific timezones.
3. **Minigames** 3. **Minigames**
- [ ] Implement a Quiz Game: - [ ] Implement a Quiz Game:
- [ ] Add a `!quiz` command to start the game. - [x] Add a `/quiz` command to start the game.
- [ ] Provide multiple-choice or true/false questions. - [ ] Provide multiple-choice or true/false questions.
- [ ] Award virtual currency for correct answers. - [ ] Award virtual currency for correct answers.
- [ ] Add a simple number-guessing game (e.g., `!guess [number]`). - [ ] Add a simple number-guessing game (e.g., `!guess [number]`).
@@ -49,8 +49,8 @@ Im going to make a To-do List for the features i want to implement for my Bot.
#### **Utility Tools** #### **Utility Tools**
1. **Search and Lookups** 1. **Search and Lookups**
- [ ] Add a `!wiki [topic]` command to fetch summaries from Wikipedia. - [x] Add a `/wiki [topic]` command to fetch summaries from Wikipedia.
- [ ] Include a `!weather [location]` command for weather updates. - [x] Include a `/weather [location]` command for weather updates.
--- ---
+1
View File
@@ -10,6 +10,7 @@ module.exports = {
.setName('user') .setName('user')
.setDescription('User to check (defaults to you)') .setDescription('User to check (defaults to you)')
.setRequired(false)), .setRequired(false)),
category: 'Currency',
async execute(interaction) { async execute(interaction) {
const dbClient = await connectToDatabase(); const dbClient = await connectToDatabase();
const db = dbClient.db('discord'); const db = dbClient.db('discord');
+1
View File
@@ -22,6 +22,7 @@ module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('earn') .setName('earn')
.setDescription('Claim your daily coin reward.'), .setDescription('Claim your daily coin reward.'),
category: 'Currency',
async execute(interaction) { async execute(interaction) {
const dbClient = await connectToDatabase(); const dbClient = await connectToDatabase();
const db = dbClient.db('discord'); const db = dbClient.db('discord');
+1
View File
@@ -17,6 +17,7 @@ module.exports = {
.setName('target') .setName('target')
.setDescription('User to receive coins') .setDescription('User to receive coins')
.setRequired(true)), .setRequired(true)),
category: 'Currency',
async execute(interaction) { async execute(interaction) {
const dbClient = await connectToDatabase(); const dbClient = await connectToDatabase();
const target = interaction.options.getUser('target'); const target = interaction.options.getUser('target');
+1
View File
@@ -11,6 +11,7 @@ module.exports = {
.setName('message') .setName('message')
.setDescription('string to store') .setDescription('string to store')
.setRequired(true)), .setRequired(true)),
category: 'Database',
async execute(interaction) { async execute(interaction) {
const dbClient = await connectToDatabase(); const dbClient = await connectToDatabase();
const message = interaction.options.getString('message'); const message = interaction.options.getString('message');
+1
View File
@@ -16,6 +16,7 @@ module.exports = {
.setDescription('Amount of coins to send') .setDescription('Amount of coins to send')
.setMinValue(1) .setMinValue(1)
.setRequired(true)), .setRequired(true)),
category: 'Currency',
async execute(interaction) { async execute(interaction) {
const dbClient = await connectToDatabase(); const dbClient = await connectToDatabase();
const db = dbClient.db('discord'); const db = dbClient.db('discord');
+10 -1
View File
@@ -1,9 +1,10 @@
const { SlashCommandBuilder } = require("discord.js"); const { SlashCommandBuilder, PermissionFlagsBits } = require("discord.js");
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName("kick") .setName("kick")
.setDescription("Kick misbehaving people") .setDescription("Kick misbehaving people")
.setDefaultMemberPermissions(PermissionFlagsBits.KickMembers)
.addUserOption(option => .addUserOption(option =>
option option
.setName('target') .setName('target')
@@ -13,6 +14,7 @@ module.exports = {
option option
.setName('reason') .setName('reason')
.setDescription('The reason of the kick')), .setDescription('The reason of the kick')),
category: 'Moderation',
async execute(interaction) { async execute(interaction) {
const user = interaction.options.getUser('target'); const user = interaction.options.getUser('target');
const reason = interaction.options.getString('reason') || 'No reason provided'; const reason = interaction.options.getString('reason') || 'No reason provided';
@@ -20,6 +22,13 @@ module.exports = {
try { try {
const member = await interaction.guild.members.fetch(user.id); const member = await interaction.guild.members.fetch(user.id);
if (!member.kickable) {
return interaction.reply({
content: 'I cannot kick that member. Check my role permissions and hierarchy.',
ephemeral: true,
});
}
await member.kick(reason); await member.kick(reason);
await interaction.reply(`Kicked ${user.tag} for: ${reason}`); await interaction.reply(`Kicked ${user.tag} for: ${reason}`);
+21 -13
View File
@@ -1,9 +1,10 @@
const { SlashCommandBuilder } = require("discord.js"); const { SlashCommandBuilder, PermissionFlagsBits } = require("discord.js");
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName("mute") .setName("mute")
.setDescription("mute misbehaving people") .setDescription("Temporarily mute a member with a timeout.")
.setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers)
.addUserOption(option => .addUserOption(option =>
option option
.setName('target') .setName('target')
@@ -12,27 +13,34 @@ module.exports = {
.addIntegerOption(option => .addIntegerOption(option =>
option option
.setName('duration') .setName('duration')
.setDescription('The duration of the Mute (Minutes)') .setDescription('The duration of the mute (in minutes)')
.setMinValue(1)
.setMaxValue(40320)
.setRequired(true)) .setRequired(true))
.addStringOption(option => .addStringOption(option =>
option option
.setName('reason') .setName('reason')
.setDescription('The reason of the mute')), .setDescription('The reason of the mute')),
category: 'Moderation',
async execute(interaction) { async execute(interaction) {
const target = interaction.options.getUser('target'); const target = interaction.options.getUser('target');
const duration = interaction.options.getInteger('duration'); const duration = interaction.options.getInteger('duration');
const reason = interaction.options.getString('reason'); const reason = interaction.options.getString('reason') || 'No reason provided';
// Convert duration to milliseconds
const durationMs = duration * 60 * 1000; const durationMs = duration * 60 * 1000;
const member = await interaction.guild.members.fetch(target.id);
const guildmember = await interaction.guild.members.fetch(target.id); if (!member.moderatable) {
return interaction.reply({
const muteRole = interaction.guild.roles.find(role => role.name === "mute"); content: 'I cannot mute that member. Check my role permissions and hierarchy.',
if (!muteRole) { ephemeral: true,
});
} else {
await guildmember.roles.add(muteRole, reason);
} }
await member.timeout(durationMs, reason);
return interaction.reply({
content: `Muted ${target.tag} for **${duration} minutes**. Reason: ${reason}`,
});
}, },
}; };
+5 -3
View File
@@ -1,15 +1,16 @@
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require("discord.js");
const { connectToDatabase } = require('../../db.js'); const { connectToDatabase } = require('../../db.js');
module.exports = { module.exports = {
mod: true, mod: true,
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName("temp-ban") .setName("temp-ban")
.setDescription("Kick misbehaving people") .setDescription("Temporarily ban a member.")
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)
.addUserOption(option => .addUserOption(option =>
option option
.setName('target') .setName('target')
.setDescription('The member to kick') .setDescription('The member to ban')
.setRequired(true)) .setRequired(true))
.addIntegerOption(option => .addIntegerOption(option =>
option option
@@ -26,6 +27,7 @@ module.exports = {
.setName('delete-messages') .setName('delete-messages')
.setDescription('Number of days to delete messages') .setDescription('Number of days to delete messages')
.setMinValue(0).setMaxValue(7)), .setMinValue(0).setMaxValue(7)),
category: 'Moderation',
async execute(interaction) { async execute(interaction) {
const user = interaction.options.getUser('target'); const user = interaction.options.getUser('target');
const reason = interaction.options.getString('reason'); const reason = interaction.options.getString('reason');
+2 -1
View File
@@ -5,6 +5,7 @@ module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName("quiz") .setName("quiz")
.setDescription("Start a cool quiz"), .setDescription("Start a cool quiz"),
category: 'Games',
async execute(interaction) { async execute(interaction) {
const item = quiz[Math.floor(Math.random() * quiz.length)]; const item = quiz[Math.floor(Math.random() * quiz.length)];
await interaction.reply({ await interaction.reply({
@@ -42,4 +43,4 @@ module.exports = {
); );
} }
}, },
}; };
+2 -1
View File
@@ -25,6 +25,7 @@ module.exports = {
{ name: 'Full date and time with weekday', value: 'F' }, { name: 'Full date and time with weekday', value: 'F' },
{ name: 'Relative time', value: 'R' }, { name: 'Relative time', value: 'R' },
)), )),
category: 'Utility',
async execute(interaction) { async execute(interaction) {
// Get user input // Get user input
const dateInput = interaction.options.getString('date'); const dateInput = interaction.options.getString('date');
@@ -57,4 +58,4 @@ module.exports = {
}); });
} }
}, },
}; };
+2 -1
View File
@@ -4,13 +4,14 @@ module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('about') .setName('about')
.setDescription('Information about this bot and creator'), .setDescription('Information about this bot and creator'),
category: 'Utility',
async execute(interaction) { async execute(interaction) {
const exampleEmbed = new EmbedBuilder() const exampleEmbed = new EmbedBuilder()
.setColor(0x0099FF) .setColor(0x0099FF)
.setTitle('About This Bot') .setTitle('About This Bot')
.setDescription('A multipurpose Discord bot focused on helpful utilities, moderation, and fun community features.') .setDescription('A multipurpose Discord bot focused on helpful utilities, moderation, and fun community features.')
.addFields( .addFields(
{ name: 'Highlights', value: 'Timezone converter, quiz games, and a growing currency system.' }, { name: 'Highlights', value: 'Timezone converter, quiz games, a currency system, and helpful utility tools.' },
{ name: 'Created By', value: interaction.client.user?.username ?? 'Bot Creator' } { name: 'Created By', value: interaction.client.user?.username ?? 'Bot Creator' }
) )
.setImage('https://i.postimg.cc/QdZZwKVD/Screenshot-2024-12-25-005933.png') .setImage('https://i.postimg.cc/QdZZwKVD/Screenshot-2024-12-25-005933.png')
+19 -4
View File
@@ -4,22 +4,37 @@ module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('help') .setName('help')
.setDescription('Gives all available commands.'), .setDescription('Gives all available commands.'),
category: 'Utility',
async execute(interaction) { async execute(interaction) {
const commandDetails = interaction.client.commands const commandDetails = interaction.client.commands
.map(cmd => ({ .map(cmd => ({
name: cmd.data.name, name: cmd.data.name,
description: cmd.data.description, description: cmd.data.description,
category: cmd.category ?? 'Other',
})) }))
.sort((a, b) => a.name.localeCompare(b.name)); .sort((a, b) => a.name.localeCompare(b.name));
const helpMessage = commandDetails const grouped = commandDetails.reduce((acc, command) => {
.map(detail => `• **/${detail.name}** — ${detail.description}`) const group = acc[command.category] ?? [];
.join('\n'); group.push(command);
acc[command.category] = group;
return acc;
}, {});
const fields = Object.keys(grouped)
.sort((a, b) => a.localeCompare(b))
.map((category) => ({
name: category,
value: grouped[category]
.map(command => `• **/${command.name}** — ${command.description}`)
.join('\n'),
}));
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(0x4b7bec) .setColor(0x4b7bec)
.setTitle('Available Commands') .setTitle('Available Commands')
.setDescription(helpMessage) .setDescription('Here is everything I can do right now.')
.addFields(fields)
.setFooter({ text: `${commandDetails.length} commands loaded.` }); .setFooter({ text: `${commandDetails.length} commands loaded.` });
await interaction.reply({ embeds: [embed], ephemeral: true }); await interaction.reply({ embeds: [embed], ephemeral: true });
+12 -4
View File
@@ -1,17 +1,25 @@
const { SlashCommandBuilder } = require('discord.js'); const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('ping') .setName('ping')
.setDescription('Check the bot\'s responsiveness.'), .setDescription('Check the bot\'s responsiveness.'),
category: 'Utility',
async execute(interaction) { async execute(interaction) {
const sentAt = Date.now(); const sentAt = Date.now();
await interaction.reply({ content: 'Pinging...', ephemeral: true }); await interaction.reply({ content: 'Pinging...', ephemeral: true });
const latency = Date.now() - sentAt; const latency = Date.now() - sentAt;
const apiLatency = Math.round(interaction.client.ws.ping); const apiLatency = Math.round(interaction.client.ws.ping);
await interaction.editReply({ const embed = new EmbedBuilder()
content: `Pong! **${latency}ms** (API: **${apiLatency}ms**)`, .setColor(0x34ace0)
}); .setTitle('Pong! 🏓')
.addFields(
{ name: 'Round Trip', value: `${latency}ms`, inline: true },
{ name: 'API Latency', value: `${apiLatency}ms`, inline: true },
)
.setTimestamp();
await interaction.editReply({ embeds: [embed], content: null });
}, },
}; };
+23 -3
View File
@@ -1,10 +1,30 @@
const { SlashCommandBuilder } = require('discord.js'); const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('server') .setName('server')
.setDescription('Provides information about the server.'), .setDescription('Provides information about the server.'),
category: 'Utility',
async execute(interaction) { async execute(interaction) {
await interaction.reply(`This server is ${interaction.guild.name} and has ${interaction.guild.memberCount} members.`); const { guild } = interaction;
const owner = await guild.fetchOwner();
const created = `<t:${Math.floor(guild.createdTimestamp / 1000)}:F>`;
const boostCount = guild.premiumSubscriptionCount ?? 0;
const embed = new EmbedBuilder()
.setColor(0x2ecc71)
.setTitle(guild.name)
.setThumbnail(guild.iconURL({ size: 256 }))
.addFields(
{ name: 'Server ID', value: guild.id, inline: true },
{ name: 'Owner', value: owner.user.tag, inline: true },
{ name: 'Members', value: guild.memberCount.toString(), inline: true },
{ name: 'Boosts', value: boostCount.toString(), inline: true },
{ name: 'Created', value: created, inline: false },
)
.setFooter({ text: `Requested by ${interaction.user.tag}` })
.setTimestamp();
await interaction.reply({ embeds: [embed], ephemeral: true });
}, },
}; };
+26 -4
View File
@@ -1,10 +1,32 @@
const { SlashCommandBuilder } = require('discord.js'); const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('user') .setName('user')
.setDescription('Provides information about the user.'), .setDescription('Provides information about a user.')
.addUserOption(option =>
option
.setName('target')
.setDescription('User to look up')
.setRequired(false)),
category: 'Utility',
async execute(interaction) { async execute(interaction) {
await interaction.reply(`This command was run by ${interaction.user.username}, who joined on ${interaction.member.joinedAt}.`); const target = interaction.options.getUser('target') ?? interaction.user;
const member = await interaction.guild.members.fetch(target.id);
const embed = new EmbedBuilder()
.setColor(0x4b7bec)
.setTitle(`${target.username}`)
.setThumbnail(target.displayAvatarURL({ size: 256 }))
.addFields(
{ name: 'Tag', value: target.tag, inline: true },
{ name: 'User ID', value: target.id, inline: true },
{ name: 'Account Created', value: `<t:${Math.floor(target.createdTimestamp / 1000)}:F>` },
{ name: 'Joined Server', value: member.joinedAt ? `<t:${Math.floor(member.joinedAt.getTime() / 1000)}:F>` : 'Unknown' },
)
.setFooter({ text: `Requested by ${interaction.user.tag}` })
.setTimestamp();
await interaction.reply({ embeds: [embed], ephemeral: true });
}, },
}; };
+72
View File
@@ -0,0 +1,72 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const getJson = async (url) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
return response.json();
};
const formatTemperature = (value, unit) => `${Math.round(value)}°${unit === 'fahrenheit' ? 'F' : 'C'}`;
module.exports = {
data: new SlashCommandBuilder()
.setName('weather')
.setDescription('Get the current weather for a location.')
.addStringOption(option =>
option
.setName('location')
.setDescription('City or place name to look up')
.setRequired(true))
.addStringOption(option =>
option
.setName('units')
.setDescription('Temperature units')
.setRequired(false)
.addChoices(
{ name: 'Celsius', value: 'celsius' },
{ name: 'Fahrenheit', value: 'fahrenheit' },
)),
category: 'Utility',
async execute(interaction) {
const location = interaction.options.getString('location');
const units = interaction.options.getString('units') ?? 'celsius';
await interaction.deferReply();
try {
const geoUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(location)}&count=1&language=en&format=json`;
const geo = await getJson(geoUrl);
const match = geo.results?.[0];
if (!match) {
return interaction.editReply({ content: 'I could not find that location. Try a more specific place name.' });
}
const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${match.latitude}&longitude=${match.longitude}&current_weather=true&temperature_unit=${units}`;
const weather = await getJson(weatherUrl);
const current = weather.current_weather;
if (!current) {
return interaction.editReply({ content: 'Weather data is currently unavailable for that location.' });
}
const embed = new EmbedBuilder()
.setColor(0x3498db)
.setTitle(`Weather for ${match.name}${match.country ? `, ${match.country}` : ''}`)
.addFields(
{ name: 'Temperature', value: formatTemperature(current.temperature, units), inline: true },
{ name: 'Wind Speed', value: `${Math.round(current.windspeed)} km/h`, inline: true },
{ name: 'Conditions', value: `Weather code: ${current.weathercode}`, inline: true },
)
.setFooter({ text: `Latitude ${match.latitude.toFixed(2)}, Longitude ${match.longitude.toFixed(2)}` })
.setTimestamp(new Date(current.time));
return interaction.editReply({ embeds: [embed] });
} catch (error) {
console.error(error);
return interaction.editReply({ content: 'Something went wrong fetching the weather. Please try again later.' });
}
},
};
+54
View File
@@ -0,0 +1,54 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const MAX_DESCRIPTION = 4096;
const truncate = (text, maxLength) => {
if (!text) return 'No summary available.';
if (text.length <= maxLength) return text;
return `${text.slice(0, maxLength - 3)}...`;
};
module.exports = {
data: new SlashCommandBuilder()
.setName('wiki')
.setDescription('Search Wikipedia for a topic.')
.addStringOption(option =>
option
.setName('topic')
.setDescription('Topic to search')
.setRequired(true)),
category: 'Utility',
async execute(interaction) {
const topic = interaction.options.getString('topic');
const endpoint = `https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(topic)}`;
await interaction.deferReply();
const response = await fetch(endpoint, {
headers: {
'User-Agent': 'DiscordBot/1.0 (https://github.com)',
},
});
if (!response.ok) {
return interaction.editReply({
content: 'I could not find a Wikipedia summary for that topic.',
});
}
const data = await response.json();
const embed = new EmbedBuilder()
.setColor(0x8e44ad)
.setTitle(data.title ?? topic)
.setURL(data.content_urls?.desktop?.page ?? `https://en.wikipedia.org/wiki/${encodeURIComponent(topic)}`)
.setDescription(truncate(data.extract, MAX_DESCRIPTION))
.setFooter({ text: 'Source: Wikipedia' });
if (data.thumbnail?.source) {
embed.setThumbnail(data.thumbnail.source);
}
return interaction.editReply({ embeds: [embed] });
},
};