mirror of
https://github.com/aydenjahola/discord-multipurpose-bot.git
synced 2024-11-21 16:25:55 +00:00
add events and spyfall game
This commit is contained in:
parent
45fdb3537f
commit
49be1ec673
19 changed files with 1311 additions and 35 deletions
|
@ -11,12 +11,23 @@ module.exports = {
|
||||||
|
|
||||||
async execute(interaction, client) {
|
async execute(interaction, client) {
|
||||||
try {
|
try {
|
||||||
// Check if the user has the Manage Roles permission
|
|
||||||
const isMod = interaction.member.permissions.has(
|
const isMod = interaction.member.permissions.has(
|
||||||
PermissionsBitField.Flags.ManageRoles
|
PermissionsBitField.Flags.ManageRoles
|
||||||
);
|
);
|
||||||
|
|
||||||
const serverName = interaction.guild.name;
|
const serverName = interaction.guild.name;
|
||||||
|
const generalCommands = [];
|
||||||
|
const modCommands = [];
|
||||||
|
|
||||||
|
// Categorize commands
|
||||||
|
client.commands.forEach((command) => {
|
||||||
|
const commandLine = `/${command.data.name} - ${command.data.description}`;
|
||||||
|
if (!command.isModOnly) {
|
||||||
|
generalCommands.push(commandLine);
|
||||||
|
} else if (isMod) {
|
||||||
|
modCommands.push(`${commandLine} (Mods only)`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const helpEmbed = new EmbedBuilder()
|
const helpEmbed = new EmbedBuilder()
|
||||||
.setColor("#0099ff")
|
.setColor("#0099ff")
|
||||||
|
@ -30,39 +41,46 @@ module.exports = {
|
||||||
iconURL: client.user.displayAvatarURL(),
|
iconURL: client.user.displayAvatarURL(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Group commands into general and mod-only
|
// Function to split commands into fields under 1024 characters
|
||||||
const generalCommands = [];
|
const addCommandFields = (embed, commands, title) => {
|
||||||
const modCommands = [];
|
let commandChunk = "";
|
||||||
|
let chunkCount = 1;
|
||||||
|
|
||||||
client.commands.forEach((command) => {
|
commands.forEach((command) => {
|
||||||
const commandLine = `/${command.data.name} - ${command.data.description}`;
|
// Check if adding this command will exceed the 1024 character limit
|
||||||
if (!command.isModOnly) {
|
if ((commandChunk + command).length > 1024) {
|
||||||
generalCommands.push(commandLine);
|
// Add current chunk as a new field
|
||||||
} else if (isMod) {
|
embed.addFields({
|
||||||
modCommands.push(`${commandLine} (Mods only)`);
|
name: `${title} (Part ${chunkCount})`,
|
||||||
|
value: commandChunk,
|
||||||
|
});
|
||||||
|
commandChunk = ""; // Reset chunk for new field
|
||||||
|
chunkCount += 1;
|
||||||
}
|
}
|
||||||
|
// Append command to the current chunk
|
||||||
|
commandChunk += command + "\n";
|
||||||
});
|
});
|
||||||
|
|
||||||
helpEmbed.addFields({
|
// Add any remaining commands in the last chunk
|
||||||
name: `General Commands (${generalCommands.length} available)`,
|
if (commandChunk) {
|
||||||
value:
|
embed.addFields({
|
||||||
generalCommands.length > 0
|
name: `${title} (Part ${chunkCount})`,
|
||||||
? generalCommands.join("\n")
|
value: commandChunk,
|
||||||
: "No general commands available.",
|
|
||||||
inline: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isMod) {
|
|
||||||
helpEmbed.addFields({
|
|
||||||
name: `Mod-Only Commands (${modCommands.length} available)`,
|
|
||||||
value:
|
|
||||||
modCommands.length > 0
|
|
||||||
? modCommands.join("\n")
|
|
||||||
: "No mod-only commands available.",
|
|
||||||
inline: false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add general commands in fields
|
||||||
|
if (generalCommands.length > 0) {
|
||||||
|
addCommandFields(helpEmbed, generalCommands, "General Commands");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add mod-only commands in fields, if user is a mod
|
||||||
|
if (isMod && modCommands.length > 0) {
|
||||||
|
addCommandFields(helpEmbed, modCommands, "Mod-Only Commands");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the single embed
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
embeds: [helpEmbed],
|
embeds: [helpEmbed],
|
||||||
});
|
});
|
||||||
|
|
34
commands/core/invite.js
Normal file
34
commands/core/invite.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("invite")
|
||||||
|
.setDescription("Provides an invite link to add the bot to your server."),
|
||||||
|
|
||||||
|
async execute(interaction, client) {
|
||||||
|
try {
|
||||||
|
const botInviteLink = `https://discord.com/oauth2/authorize?client_id=${client.user.id}&permissions=8&scope=bot%20applications.commands`;
|
||||||
|
|
||||||
|
const inviteEmbed = new EmbedBuilder()
|
||||||
|
.setColor("#0099ff")
|
||||||
|
.setTitle("Invite the Bot to Your Server!")
|
||||||
|
.setDescription(`**[Click here to invite the bot!](${botInviteLink})**`)
|
||||||
|
.setTimestamp()
|
||||||
|
.setFooter({
|
||||||
|
text: `Requested by ${interaction.user.tag}`,
|
||||||
|
iconURL: interaction.user.displayAvatarURL(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [inviteEmbed],
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error executing the invite command:", error);
|
||||||
|
await interaction.reply({
|
||||||
|
content: "There was an error while executing this command!",
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
113
commands/events/createEvent.js
Normal file
113
commands/events/createEvent.js
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
const { SlashCommandBuilder } = require("discord.js");
|
||||||
|
const Event = require("../../models/Event");
|
||||||
|
const Participant = require("../../models/Participant");
|
||||||
|
const moment = require("moment");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("createevent")
|
||||||
|
.setDescription("Create a new event.")
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("name")
|
||||||
|
.setDescription("Name of the event")
|
||||||
|
.setRequired(true)
|
||||||
|
)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("description")
|
||||||
|
.setDescription("Description of the event")
|
||||||
|
.setRequired(true)
|
||||||
|
)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("category")
|
||||||
|
.setDescription("Category of the event")
|
||||||
|
.addChoices(
|
||||||
|
{ name: "Tournament", value: "tournament" },
|
||||||
|
{ name: "Meeting", value: "meeting" },
|
||||||
|
{ name: "Giveaway", value: "giveaway" },
|
||||||
|
{ name: "Other", value: "other" }
|
||||||
|
)
|
||||||
|
.setRequired(true)
|
||||||
|
)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("location")
|
||||||
|
.setDescription("Location of the event (default is Online)")
|
||||||
|
.setRequired(true)
|
||||||
|
)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("startdatetime")
|
||||||
|
.setDescription("Start date and time of the event (YYYY-MM-DD HH:mm)")
|
||||||
|
.setRequired(true)
|
||||||
|
)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("enddatetime")
|
||||||
|
.setDescription("End date and time of the event (YYYY-MM-DD HH:mm)")
|
||||||
|
.setRequired(true)
|
||||||
|
)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("recurrence")
|
||||||
|
.setDescription("Recurrence of the event")
|
||||||
|
.addChoices(
|
||||||
|
{ name: "None", value: "none" },
|
||||||
|
{ name: "Daily", value: "daily" },
|
||||||
|
{ name: "Weekly", value: "weekly" },
|
||||||
|
{ name: "Monthly", value: "monthly" }
|
||||||
|
)
|
||||||
|
),
|
||||||
|
async execute(interaction) {
|
||||||
|
const name = interaction.options.getString("name");
|
||||||
|
const description = interaction.options.getString("description");
|
||||||
|
const category = interaction.options.getString("category");
|
||||||
|
const location = interaction.options.getString("location") || "Online";
|
||||||
|
const startDate = moment(
|
||||||
|
interaction.options.getString("startdatetime"),
|
||||||
|
"YYYY-MM-DD HH:mm"
|
||||||
|
);
|
||||||
|
const endDate = moment(
|
||||||
|
interaction.options.getString("enddatetime"),
|
||||||
|
"YYYY-MM-DD HH:mm"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!startDate.isValid() || !endDate.isValid()) {
|
||||||
|
return await interaction.reply(
|
||||||
|
"Invalid date format! Use YYYY-MM-DD HH:mm."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startDate.isAfter(endDate)) {
|
||||||
|
return await interaction.reply("Start date must be before the end date.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new event
|
||||||
|
const event = new Event({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
category,
|
||||||
|
location,
|
||||||
|
startDate: startDate.toISOString(),
|
||||||
|
endDate: endDate.toISOString(),
|
||||||
|
organizerId: interaction.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await event.save();
|
||||||
|
await interaction.reply(
|
||||||
|
`Event "${name}" created! Starts at ${startDate.format(
|
||||||
|
"MMMM Do YYYY, h:mm a"
|
||||||
|
)} and ends at ${endDate.format("MMMM Do YYYY, h:mm a")}.`
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 11000) {
|
||||||
|
await interaction.reply("An event with that name already exists.");
|
||||||
|
} else {
|
||||||
|
await interaction.reply("Error creating event. Please try again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
150
commands/events/editEvent.js
Normal file
150
commands/events/editEvent.js
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
// commands/editevent.js
|
||||||
|
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||||
|
const Event = require("../../models/Event");
|
||||||
|
const moment = require("moment");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("editevent")
|
||||||
|
.setDescription("Edit an existing event.")
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("name")
|
||||||
|
.setDescription("Name of the event to edit")
|
||||||
|
.setRequired(true)
|
||||||
|
)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("description")
|
||||||
|
.setDescription("New description of the event")
|
||||||
|
)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("category")
|
||||||
|
.setDescription("New category of the event")
|
||||||
|
.addChoices(
|
||||||
|
{ name: "Tournament", value: "tournament" },
|
||||||
|
{ name: "Meeting", value: "meeting" },
|
||||||
|
{ name: "Giveaway", value: "giveaway" },
|
||||||
|
{ name: "Other", value: "other" }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option.setName("location").setDescription("New location of the event")
|
||||||
|
)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("startdatetime")
|
||||||
|
.setDescription(
|
||||||
|
"New start date and time of the event (YYYY-MM-DD HH:mm)"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("enddatetime")
|
||||||
|
.setDescription("New end date and time of the event (YYYY-MM-DD HH:mm)")
|
||||||
|
)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("recurrence")
|
||||||
|
.setDescription("New recurrence of the event")
|
||||||
|
.addChoices(
|
||||||
|
{ name: "None", value: "none" },
|
||||||
|
{ name: "Daily", value: "daily" },
|
||||||
|
{ name: "Weekly", value: "weekly" },
|
||||||
|
{ name: "Monthly", value: "monthly" }
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
const name = interaction.options.getString("name");
|
||||||
|
const event = await Event.findOne({ name });
|
||||||
|
|
||||||
|
if (!event) {
|
||||||
|
return await interaction.reply("Event not found!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interaction.options.getString("description")) {
|
||||||
|
event.description = interaction.options.getString("description");
|
||||||
|
}
|
||||||
|
if (interaction.options.getString("category")) {
|
||||||
|
event.category = interaction.options.getString("category");
|
||||||
|
}
|
||||||
|
if (interaction.options.getString("location")) {
|
||||||
|
event.location = interaction.options.getString("location");
|
||||||
|
}
|
||||||
|
if (interaction.options.getString("startdatetime")) {
|
||||||
|
const newStartDate = moment(
|
||||||
|
interaction.options.getString("startdatetime"),
|
||||||
|
"YYYY-MM-DD HH:mm"
|
||||||
|
);
|
||||||
|
if (!newStartDate.isValid()) {
|
||||||
|
return await interaction.reply(
|
||||||
|
"Invalid start date format! Use YYYY-MM-DD HH:mm."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
event.startDate = newStartDate.toISOString();
|
||||||
|
}
|
||||||
|
if (interaction.options.getString("enddatetime")) {
|
||||||
|
const newEndDate = moment(
|
||||||
|
interaction.options.getString("enddatetime"),
|
||||||
|
"YYYY-MM-DD HH:mm"
|
||||||
|
);
|
||||||
|
if (!newEndDate.isValid()) {
|
||||||
|
return await interaction.reply(
|
||||||
|
"Invalid end date format! Use YYYY-MM-DD HH:mm."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (moment(event.startDate).isAfter(newEndDate)) {
|
||||||
|
return await interaction.reply(
|
||||||
|
"End date must be after the start date."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
event.endDate = newEndDate.toISOString();
|
||||||
|
}
|
||||||
|
if (interaction.options.getString("recurrence")) {
|
||||||
|
event.recurrence = interaction.options.getString("recurrence");
|
||||||
|
}
|
||||||
|
|
||||||
|
await event.save();
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle(`Event "${event.name}" Updated Successfully`)
|
||||||
|
.setColor("#00FF00")
|
||||||
|
.addFields(
|
||||||
|
{
|
||||||
|
name: "Description",
|
||||||
|
value: event.description || "No description provided",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Category",
|
||||||
|
value: event.category || "Not specified",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Location",
|
||||||
|
value: event.location || "Not specified",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Start Date",
|
||||||
|
value: moment(event.startDate).format("YYYY-MM-DD HH:mm"),
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "End Date",
|
||||||
|
value: moment(event.endDate).format("YYYY-MM-DD HH:mm"),
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{ name: "Recurrence", value: event.recurrence || "None", inline: true }
|
||||||
|
)
|
||||||
|
.setFooter({
|
||||||
|
text: "Event updated successfully",
|
||||||
|
iconURL: interaction.user.displayAvatarURL(),
|
||||||
|
})
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [embed] });
|
||||||
|
},
|
||||||
|
};
|
92
commands/events/joinEvent.js
Normal file
92
commands/events/joinEvent.js
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||||
|
const Event = require("../../models/Event");
|
||||||
|
const Participant = require("../../models/Participant");
|
||||||
|
const moment = require("moment");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("joinevent")
|
||||||
|
.setDescription("Join an event.")
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("event_name")
|
||||||
|
.setDescription("Name of the event to join")
|
||||||
|
.setRequired(true)
|
||||||
|
),
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
const eventName = interaction.options.getString("event_name");
|
||||||
|
const event = await Event.findOne({ name: eventName });
|
||||||
|
|
||||||
|
if (!event) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: `Event "${eventName}" not found.`,
|
||||||
|
ephemeral: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let participant = await Participant.findOne({
|
||||||
|
userId: interaction.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!participant) {
|
||||||
|
participant = new Participant({
|
||||||
|
userId: interaction.user.id,
|
||||||
|
username: interaction.user.username,
|
||||||
|
});
|
||||||
|
await participant.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!event.participants.includes(participant._id)) {
|
||||||
|
event.participants.push(participant._id);
|
||||||
|
await event.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle(`Successfully Joined Event: ${event.name}`)
|
||||||
|
.setColor("#3498db")
|
||||||
|
.addFields(
|
||||||
|
{
|
||||||
|
name: "Category",
|
||||||
|
value: event.category ? String(event.category) : "Not specified",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Location",
|
||||||
|
value: event.location ? String(event.location) : "Virtual",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Start Date",
|
||||||
|
value: moment(event.startDate).isValid()
|
||||||
|
? moment(event.startDate).format("YYYY-MM-DD HH:mm")
|
||||||
|
: "N/A",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "End Date",
|
||||||
|
value: moment(event.endDate).isValid()
|
||||||
|
? moment(event.endDate).format("YYYY-MM-DD HH:mm")
|
||||||
|
: "N/A",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Recurrence",
|
||||||
|
value: event.recurrence ? String(event.recurrence) : "None",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Participants",
|
||||||
|
value: String(event.participants.length),
|
||||||
|
inline: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.setTimestamp()
|
||||||
|
.setFooter({
|
||||||
|
text: `Joined by ${interaction.user.username}`,
|
||||||
|
iconURL: interaction.user.displayAvatarURL(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [embed] });
|
||||||
|
},
|
||||||
|
};
|
72
commands/events/leaveEvent.js
Normal file
72
commands/events/leaveEvent.js
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||||
|
const Event = require("../../models/Event");
|
||||||
|
const Participant = require("../../models/Participant");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("leaveevent")
|
||||||
|
.setDescription("Leave an event.")
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("event_name")
|
||||||
|
.setDescription("Name of the event to leave")
|
||||||
|
.setRequired(true)
|
||||||
|
),
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
const eventName = interaction.options.getString("event_name");
|
||||||
|
const event = await Event.findOne({ name: eventName });
|
||||||
|
|
||||||
|
if (!event) {
|
||||||
|
return await interaction.reply({
|
||||||
|
content: `Event "${eventName}" not found.`,
|
||||||
|
ephemeral: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const participant = await Participant.findOne({
|
||||||
|
userId: interaction.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!participant) {
|
||||||
|
return await interaction.reply({
|
||||||
|
content: `You are not a participant of this event.`,
|
||||||
|
ephemeral: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
event.participants.pull(participant._id);
|
||||||
|
await event.save();
|
||||||
|
|
||||||
|
const isInOtherEvents = await Event.exists({
|
||||||
|
participants: participant._id,
|
||||||
|
});
|
||||||
|
if (!isInOtherEvents) {
|
||||||
|
await Participant.deleteOne({ userId: interaction.user.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle(`Left Event: ${event.name}`)
|
||||||
|
.setColor("#e74c3c")
|
||||||
|
.addFields(
|
||||||
|
{
|
||||||
|
name: "Category",
|
||||||
|
value: event.category || "Not specified",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{ name: "Location", value: event.location || "Virtual", inline: true },
|
||||||
|
{
|
||||||
|
name: "Participants Remaining",
|
||||||
|
value: String(event.participants.length),
|
||||||
|
inline: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.setFooter({
|
||||||
|
text: `Left by ${interaction.user.username}`,
|
||||||
|
iconURL: interaction.user.displayAvatarURL(),
|
||||||
|
})
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [embed] });
|
||||||
|
},
|
||||||
|
};
|
51
commands/events/listEvents.js
Normal file
51
commands/events/listEvents.js
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||||
|
const Event = require("../../models/Event");
|
||||||
|
const moment = require("moment");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("listevents")
|
||||||
|
.setDescription("List all upcoming events."),
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
const { user } = interaction;
|
||||||
|
const upcomingEvents = await Event.find({ status: "upcoming" }).populate(
|
||||||
|
"participants"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (upcomingEvents.length === 0) {
|
||||||
|
return interaction.reply("There are no upcoming events.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle("Upcoming Events")
|
||||||
|
.setColor("#00FF00")
|
||||||
|
.setTimestamp()
|
||||||
|
.setFooter({
|
||||||
|
text: `Requested by ${user.username}`,
|
||||||
|
iconURL: user.displayAvatarURL(),
|
||||||
|
});
|
||||||
|
|
||||||
|
upcomingEvents.forEach((event) => {
|
||||||
|
const participantsList =
|
||||||
|
event.participants.map((p) => p.username).join(", ") ||
|
||||||
|
"No Particiapnts yet";
|
||||||
|
|
||||||
|
embed.addFields({
|
||||||
|
name: event.name,
|
||||||
|
value: `**Description:** ${event.description}\n**Category:** ${
|
||||||
|
event.category
|
||||||
|
}\n**Location:** ${event.location}\n**Start Date:** ${moment(
|
||||||
|
event.startDate
|
||||||
|
).format("YYYY-MM-DD HH:mm")}\n**End Date:** ${moment(
|
||||||
|
event.endDate
|
||||||
|
).format("YYYY-MM-DD HH:mm")}\n**Recurrence:** ${
|
||||||
|
event.recurrence
|
||||||
|
}\n**Participants:** ${participantsList}`,
|
||||||
|
inline: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [embed] });
|
||||||
|
},
|
||||||
|
};
|
401
commands/games/spyfall.js
Normal file
401
commands/games/spyfall.js
Normal file
|
@ -0,0 +1,401 @@
|
||||||
|
const {
|
||||||
|
SlashCommandBuilder,
|
||||||
|
EmbedBuilder,
|
||||||
|
ButtonBuilder,
|
||||||
|
ActionRowBuilder,
|
||||||
|
ButtonStyle,
|
||||||
|
} = require("discord.js");
|
||||||
|
const SpyfallGame = require("../../models/SpyfallGame");
|
||||||
|
const SpyfallLocation = require("../../models/SpyfallLocation");
|
||||||
|
const { v4: uuidv4 } = require("uuid");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("spyfall")
|
||||||
|
.setDescription("Start a game of Spyfall."),
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
const guildId = interaction.guild.id;
|
||||||
|
const gameId = uuidv4();
|
||||||
|
|
||||||
|
const existingGame = await SpyfallGame.findOne({ gameId });
|
||||||
|
if (existingGame && existingGame.status === "ongoing") {
|
||||||
|
return interaction.reply(
|
||||||
|
"A game is already in progress! Please finish the current game first."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const locations = await SpyfallLocation.find({});
|
||||||
|
|
||||||
|
if (!locations || locations.length === 0) {
|
||||||
|
return interaction.reply("No locations found. Please try again later.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedLocation =
|
||||||
|
locations[Math.floor(Math.random() * locations.length)].name;
|
||||||
|
|
||||||
|
const players = new Set();
|
||||||
|
|
||||||
|
const joinEmbed = new EmbedBuilder()
|
||||||
|
.setColor("#ffcc00")
|
||||||
|
.setTitle("Join the Spyfall Game!")
|
||||||
|
.setDescription("Click the button below to join the game.")
|
||||||
|
.addFields({ name: "Current Players:", value: "None yet." })
|
||||||
|
.setFooter({ text: "You need at least 2 players to start!" })
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
const joinButton = new ButtonBuilder()
|
||||||
|
.setCustomId("join_game")
|
||||||
|
.setLabel("Join Game")
|
||||||
|
.setStyle(ButtonStyle.Success);
|
||||||
|
|
||||||
|
const leaveButton = new ButtonBuilder()
|
||||||
|
.setCustomId("leave_game")
|
||||||
|
.setLabel("Leave Game")
|
||||||
|
.setStyle(ButtonStyle.Danger);
|
||||||
|
|
||||||
|
const startButton = new ButtonBuilder()
|
||||||
|
.setCustomId("start_game")
|
||||||
|
.setLabel("Start Game")
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
.setDisabled(true);
|
||||||
|
|
||||||
|
const buttonRow = new ActionRowBuilder().addComponents(
|
||||||
|
joinButton,
|
||||||
|
leaveButton,
|
||||||
|
startButton
|
||||||
|
);
|
||||||
|
|
||||||
|
const joinMessage = await interaction.reply({
|
||||||
|
embeds: [joinEmbed],
|
||||||
|
components: [buttonRow],
|
||||||
|
fetchReply: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const filter = (i) =>
|
||||||
|
i.customId === "join_game" ||
|
||||||
|
i.customId === "leave_game" ||
|
||||||
|
i.customId === "start_game";
|
||||||
|
|
||||||
|
const collector = joinMessage.createMessageComponentCollector({ filter });
|
||||||
|
|
||||||
|
collector.on("collect", async (i) => {
|
||||||
|
try {
|
||||||
|
if (!players.has(i.user.id) && i.customId !== "join_game") {
|
||||||
|
return i.reply({
|
||||||
|
content: "You cannot interact with this button.",
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i.customId === "join_game") {
|
||||||
|
if (players.has(i.user.id)) {
|
||||||
|
return i.reply({
|
||||||
|
content: "You are already in the game!",
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
players.add(i.user.id);
|
||||||
|
const currentPlayers =
|
||||||
|
Array.from(players)
|
||||||
|
.map(
|
||||||
|
(id) => interaction.guild.members.cache.get(id).user.username
|
||||||
|
)
|
||||||
|
.join(", ") || "None yet.";
|
||||||
|
|
||||||
|
joinEmbed
|
||||||
|
.setDescription("Click the button below to join the game.")
|
||||||
|
.setFields([{ name: "Current Players:", value: currentPlayers }]);
|
||||||
|
|
||||||
|
if (players.size >= 2) {
|
||||||
|
startButton.setDisabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await i.update({ embeds: [joinEmbed], components: [buttonRow] });
|
||||||
|
} else if (i.customId === "leave_game") {
|
||||||
|
if (players.has(i.user.id)) {
|
||||||
|
players.delete(i.user.id);
|
||||||
|
const currentPlayers =
|
||||||
|
Array.from(players)
|
||||||
|
.map(
|
||||||
|
(id) =>
|
||||||
|
interaction.guild.members.cache.get(id).user.username
|
||||||
|
)
|
||||||
|
.join(", ") || "None yet.";
|
||||||
|
|
||||||
|
joinEmbed
|
||||||
|
.setDescription("Click the button below to join the game.")
|
||||||
|
.setFields([
|
||||||
|
{ name: "Current Players:", value: currentPlayers },
|
||||||
|
]);
|
||||||
|
await i.update({ embeds: [joinEmbed], components: [buttonRow] });
|
||||||
|
} else {
|
||||||
|
await i.reply({
|
||||||
|
content: "You are not part of the game.",
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (i.customId === "start_game") {
|
||||||
|
if (players.size < 2) {
|
||||||
|
return i.reply({
|
||||||
|
content: "You need at least 2 players to start the game.",
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const spyIndex = Math.floor(Math.random() * players.size);
|
||||||
|
const spy = Array.from(players)[spyIndex];
|
||||||
|
|
||||||
|
const newGame = new SpyfallGame({
|
||||||
|
gameId,
|
||||||
|
guildId,
|
||||||
|
location: selectedLocation,
|
||||||
|
spy,
|
||||||
|
players: Array.from(players),
|
||||||
|
status: "ongoing",
|
||||||
|
});
|
||||||
|
|
||||||
|
await newGame.save();
|
||||||
|
|
||||||
|
const locationMessage = `The game has started! The location is **${selectedLocation}**.`;
|
||||||
|
|
||||||
|
for (const playerId of players) {
|
||||||
|
const player = await interaction.guild.members.cache.get(playerId)
|
||||||
|
.user;
|
||||||
|
|
||||||
|
if (playerId !== spy) {
|
||||||
|
await player.send(locationMessage);
|
||||||
|
} else {
|
||||||
|
await player.send(
|
||||||
|
"The game has started! You are the spy! Try to blend in!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setColor("#ffcc00")
|
||||||
|
.setTitle("Spyfall Game Started!")
|
||||||
|
.setDescription("The game has started! One of you is the spy!")
|
||||||
|
.setFooter({
|
||||||
|
text: "The spy must figure out the location without revealing themselves!",
|
||||||
|
})
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
await interaction.followUp({ embeds: [embed], components: [] });
|
||||||
|
collector.stop();
|
||||||
|
await interaction.channel.send({
|
||||||
|
content: "The game has started! Use `/stopspyfall` to end it.",
|
||||||
|
});
|
||||||
|
|
||||||
|
await startQuestioningPhase(
|
||||||
|
interaction,
|
||||||
|
spy,
|
||||||
|
players,
|
||||||
|
selectedLocation,
|
||||||
|
gameId
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error during button interaction:", error);
|
||||||
|
await i.reply({
|
||||||
|
content:
|
||||||
|
"There was an error processing your request. Please try again later.",
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching locations or starting the game:", error);
|
||||||
|
await interaction.reply(
|
||||||
|
"There was an error fetching locations or starting the game. Please try again later."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function startQuestioningPhase(
|
||||||
|
interaction,
|
||||||
|
spy,
|
||||||
|
players,
|
||||||
|
selectedLocation,
|
||||||
|
gameId
|
||||||
|
) {
|
||||||
|
const ongoingGame = await SpyfallGame.findOne({ gameId });
|
||||||
|
|
||||||
|
if (!ongoingGame || ongoingGame.status !== "ongoing") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const playerArray = Array.from(players);
|
||||||
|
let currentIndex = 0;
|
||||||
|
|
||||||
|
async function askQuestion() {
|
||||||
|
if (currentIndex >= playerArray.length) {
|
||||||
|
return startVotingPhase(interaction, spy, playerArray, gameId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPlayerId = playerArray[currentIndex];
|
||||||
|
const currentPlayer =
|
||||||
|
interaction.guild.members.cache.get(currentPlayerId).user;
|
||||||
|
|
||||||
|
const questionEmbed = new EmbedBuilder()
|
||||||
|
.setColor("#ffcc00")
|
||||||
|
.setTitle("Your Turn to Ask a Question!")
|
||||||
|
.setDescription(
|
||||||
|
`It's **${currentPlayer.username}**'s turn to ask a question about the location.`
|
||||||
|
)
|
||||||
|
.setFooter({ text: "Click 'Finish Turn' when you're done." });
|
||||||
|
|
||||||
|
const finishButton = new ButtonBuilder()
|
||||||
|
.setCustomId(`finish_turn_${currentPlayerId}`)
|
||||||
|
.setLabel("Finish Turn")
|
||||||
|
.setStyle(ButtonStyle.Primary);
|
||||||
|
|
||||||
|
const buttonRow = new ActionRowBuilder().addComponents(finishButton);
|
||||||
|
|
||||||
|
await interaction.channel.send({
|
||||||
|
embeds: [questionEmbed],
|
||||||
|
components: [buttonRow],
|
||||||
|
});
|
||||||
|
|
||||||
|
const filter = (i) =>
|
||||||
|
i.customId === `finish_turn_${currentPlayerId}` &&
|
||||||
|
i.user.id === currentPlayerId;
|
||||||
|
const collector = interaction.channel.createMessageComponentCollector({
|
||||||
|
filter,
|
||||||
|
time: 30000,
|
||||||
|
});
|
||||||
|
|
||||||
|
collector.on("collect", async (i) => {
|
||||||
|
await i.reply({ content: "Your turn has ended!", ephemeral: true });
|
||||||
|
collector.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
collector.on("end", async () => {
|
||||||
|
currentIndex++;
|
||||||
|
await askQuestion();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
askQuestion();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startVotingPhase(
|
||||||
|
interaction,
|
||||||
|
spy,
|
||||||
|
players,
|
||||||
|
gameId,
|
||||||
|
selectedLocation
|
||||||
|
) {
|
||||||
|
// Ensure players is an array
|
||||||
|
if (!Array.isArray(players)) {
|
||||||
|
console.error("Players is not an array:", players);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ongoingGame = await SpyfallGame.findOne({ gameId });
|
||||||
|
|
||||||
|
if (!ongoingGame || ongoingGame.status !== "ongoing") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const votes = new Map();
|
||||||
|
const voteEmbed = new EmbedBuilder()
|
||||||
|
.setColor("#ffcc00")
|
||||||
|
.setTitle("Voting Phase")
|
||||||
|
.setDescription(
|
||||||
|
"Vote for who you think the spy is! (You cannot vote for yourself)"
|
||||||
|
)
|
||||||
|
.setFooter({ text: "Click the buttons below to vote." });
|
||||||
|
|
||||||
|
const voteButtons = players.map((playerId) =>
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`vote_${playerId}`)
|
||||||
|
.setLabel(interaction.guild.members.cache.get(playerId).user.username)
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
);
|
||||||
|
|
||||||
|
const voteRow = new ActionRowBuilder().addComponents(voteButtons);
|
||||||
|
const voteMessage = await interaction.channel.send({
|
||||||
|
embeds: [voteEmbed],
|
||||||
|
components: [voteRow],
|
||||||
|
});
|
||||||
|
|
||||||
|
const voteFilter = (i) =>
|
||||||
|
i.customId.startsWith("vote_") && players.includes(i.user.id);
|
||||||
|
const voteCollector = voteMessage.createMessageComponentCollector({
|
||||||
|
filter: voteFilter,
|
||||||
|
});
|
||||||
|
|
||||||
|
voteCollector.on("collect", async (i) => {
|
||||||
|
const votedPlayerId = i.customId.split("_")[1];
|
||||||
|
|
||||||
|
if (i.user.id === votedPlayerId) {
|
||||||
|
await i.reply({
|
||||||
|
content: "You cannot vote for yourself.",
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the vote
|
||||||
|
votes.set(votedPlayerId, (votes.get(votedPlayerId) || 0) + 1);
|
||||||
|
|
||||||
|
await i.reply({
|
||||||
|
content: `You voted for ${
|
||||||
|
interaction.guild.members.cache.get(votedPlayerId).user.username
|
||||||
|
}.`,
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (votes.size === players.length) {
|
||||||
|
voteCollector.stop();
|
||||||
|
await revealSpy(interaction, spy, votes, selectedLocation, gameId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
voteCollector.on("end", async () => {
|
||||||
|
if (votes.size > 0) {
|
||||||
|
await revealSpy(interaction, spy, votes, selectedLocation, gameId);
|
||||||
|
} else {
|
||||||
|
await interaction.channel.send("Voting timed out! No votes were cast.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function revealSpy(interaction, spy, votes, selectedLocation, gameId) {
|
||||||
|
const voteEntries = Array.from(votes.entries());
|
||||||
|
const highestVote = Math.max(...voteEntries.map(([_, v]) => v));
|
||||||
|
const suspectedSpy = voteEntries.find(([_, v]) => v === highestVote)[0];
|
||||||
|
|
||||||
|
const resultEmbed = new EmbedBuilder()
|
||||||
|
.setColor("#ffcc00")
|
||||||
|
.setTitle("Voting Results")
|
||||||
|
.addFields(
|
||||||
|
{
|
||||||
|
name: "Suspected Spy:",
|
||||||
|
value: interaction.guild.members.cache.get(suspectedSpy).user.username,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Actual Spy:",
|
||||||
|
value: interaction.guild.members.cache.get(spy).user.username,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.setFooter({
|
||||||
|
text:
|
||||||
|
suspectedSpy === spy
|
||||||
|
? "The spy has been caught!"
|
||||||
|
: "The spy has escaped!",
|
||||||
|
})
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
const result = await SpyfallGame.updateOne({ gameId }, { status: "ended" });
|
||||||
|
if (result.modifiedCount === 0) {
|
||||||
|
console.log("No game found or status was already 'ended'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.channel.send({ embeds: [resultEmbed] });
|
||||||
|
}
|
38
commands/games/stopspyfall.js
Normal file
38
commands/games/stopspyfall.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
const { SlashCommandBuilder } = require("discord.js");
|
||||||
|
const SpyfallGame = require("../../models/SpyfallGame");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("stopspyfall")
|
||||||
|
.setDescription("Stop the current Spyfall game in this server."),
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
try {
|
||||||
|
const guildId = interaction.guild.id;
|
||||||
|
|
||||||
|
const ongoingGame = await SpyfallGame.findOne({
|
||||||
|
guildId: guildId,
|
||||||
|
status: "ongoing",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!ongoingGame) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: "No Spyfall game is currently in progress in this server!",
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ongoingGame.status = "ended";
|
||||||
|
await ongoingGame.save();
|
||||||
|
|
||||||
|
await interaction.reply("The Spyfall game has been stopped!");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error stopping the game:", error);
|
||||||
|
await interaction.reply({
|
||||||
|
content:
|
||||||
|
"There was an error trying to stop the game. Please try again later.",
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
61
commands/general/thisDayInHistory.js
Normal file
61
commands/general/thisDayInHistory.js
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||||
|
const axios = require("axios");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("thisdayinhistory")
|
||||||
|
.setDescription("Shows historical events that happened on this day."),
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
try {
|
||||||
|
const today = new Date();
|
||||||
|
const month = today.getMonth() + 1;
|
||||||
|
const day = today.getDate();
|
||||||
|
|
||||||
|
const response = await axios.get(
|
||||||
|
`https://en.wikipedia.org/api/rest_v1/feed/onthisday/events/${month}/${day}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.data.events.length === 0) {
|
||||||
|
return interaction.reply("No significant events found for today.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const events = response.data.events
|
||||||
|
.map((event) => `${event.year}: ${event.text}`)
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
const maxDescriptionLength = 4096;
|
||||||
|
const truncatedEvents =
|
||||||
|
events.length > maxDescriptionLength
|
||||||
|
? events.slice(0, maxDescriptionLength - 3) + "..."
|
||||||
|
: events;
|
||||||
|
|
||||||
|
const historyEmbed = new EmbedBuilder()
|
||||||
|
.setColor("#0099ff")
|
||||||
|
.setTitle(`This Day in History: ${month}/${day}`)
|
||||||
|
.setDescription(truncatedEvents)
|
||||||
|
.setTimestamp()
|
||||||
|
.setFooter({
|
||||||
|
text: `Requested by ${interaction.user.tag}`,
|
||||||
|
iconURL: interaction.user.displayAvatarURL(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [historyEmbed] });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error executing the thisdayinhistory command:", error);
|
||||||
|
|
||||||
|
if (error.response) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: `API returned an error: ${error.response.status} - ${error.response.data.title}`,
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await interaction.reply({
|
||||||
|
content:
|
||||||
|
"There was an error while executing this command! Please try again later.",
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
64
commands/moderation/Servers.js
Normal file
64
commands/moderation/Servers.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
const {
|
||||||
|
SlashCommandBuilder,
|
||||||
|
EmbedBuilder,
|
||||||
|
PermissionsBitField,
|
||||||
|
} = require("discord.js");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("servers")
|
||||||
|
.setDescription("Displays a list of servers the bot is currently in"),
|
||||||
|
isModOnly: true,
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
try {
|
||||||
|
// Check if the user has the Manage Server permission
|
||||||
|
if (
|
||||||
|
!interaction.member.permissions.has(
|
||||||
|
PermissionsBitField.Flags.ManageGuild
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: "You do not have permission to use this command!",
|
||||||
|
ephemeral: false,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const guilds = interaction.client.guilds.cache.map((guild) => ({
|
||||||
|
name: guild.name,
|
||||||
|
memberCount: guild.memberCount,
|
||||||
|
id: guild.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const serversEmbed = new EmbedBuilder()
|
||||||
|
.setColor("#0099ff")
|
||||||
|
.setTitle("Servers the Bot is In")
|
||||||
|
.setDescription(`Currently in ${guilds.length} servers`)
|
||||||
|
.setTimestamp()
|
||||||
|
.setFooter({
|
||||||
|
text: `Requested by ${interaction.user.tag}`,
|
||||||
|
iconURL: interaction.user.displayAvatarURL(),
|
||||||
|
});
|
||||||
|
|
||||||
|
guilds.forEach((guild) => {
|
||||||
|
serversEmbed.addFields({
|
||||||
|
name: guild.name,
|
||||||
|
value: `ID: ${guild.id}\nMembers: ${guild.memberCount}`,
|
||||||
|
inline: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [serversEmbed],
|
||||||
|
ephemeral: false,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error executing servers command:", error);
|
||||||
|
await interaction.reply({
|
||||||
|
content: "There was an error while executing this command!",
|
||||||
|
ephemeral: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
63
commands/utils/stats.js
Normal file
63
commands/utils/stats.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("stats")
|
||||||
|
.setDescription("Displays server statistics."),
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
try {
|
||||||
|
const totalMembers = interaction.guild.memberCount;
|
||||||
|
|
||||||
|
const onlineMembers = interaction.guild.members.cache.filter(
|
||||||
|
(member) => member.presence?.status !== "offline"
|
||||||
|
).size;
|
||||||
|
|
||||||
|
const offlineMembers = totalMembers - onlineMembers;
|
||||||
|
const humanMembers = interaction.guild.members.cache.filter(
|
||||||
|
(member) => !member.user.bot
|
||||||
|
).size;
|
||||||
|
const botMembers = totalMembers - humanMembers;
|
||||||
|
|
||||||
|
const statsEmbed = new EmbedBuilder()
|
||||||
|
.setColor("#0099ff")
|
||||||
|
.setTitle("Server Statistics")
|
||||||
|
.addFields(
|
||||||
|
{
|
||||||
|
name: "Total Members",
|
||||||
|
value: totalMembers.toString(),
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Online Members",
|
||||||
|
value: onlineMembers.toString(),
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Offline Members",
|
||||||
|
value: offlineMembers.toString(),
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Human Members",
|
||||||
|
value: humanMembers.toString(),
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{ name: "Bot Members", value: botMembers.toString(), inline: true }
|
||||||
|
)
|
||||||
|
.setTimestamp()
|
||||||
|
.setFooter({
|
||||||
|
text: `Requested by ${interaction.user.tag}`,
|
||||||
|
iconURL: interaction.user.displayAvatarURL(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [statsEmbed] });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error executing the stats command:", error);
|
||||||
|
await interaction.reply({
|
||||||
|
content: "There was an error while executing this command!",
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
10
index.js
10
index.js
|
@ -11,6 +11,7 @@ const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const ServerSettings = require("./models/ServerSettings");
|
const ServerSettings = require("./models/ServerSettings");
|
||||||
const seedShopItems = require("./utils/seedShopItems");
|
const seedShopItems = require("./utils/seedShopItems");
|
||||||
|
const seedSpyfallLocations = require("./utils/seedSpyfallLocations");
|
||||||
|
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
intents: [
|
intents: [
|
||||||
|
@ -67,10 +68,8 @@ client.once("ready", async () => {
|
||||||
|
|
||||||
// Seed the shop items
|
// Seed the shop items
|
||||||
for (const guildId of guilds) {
|
for (const guildId of guilds) {
|
||||||
await seedShopItems(guildId); // Pass guildId to seedShopItems
|
await seedShopItems(guildId);
|
||||||
}
|
await seedSpyfallLocations(guildId);
|
||||||
|
|
||||||
for (const guildId of guilds) {
|
|
||||||
await registerCommands(guildId);
|
await registerCommands(guildId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,6 +92,9 @@ client.on("guildCreate", async (guild) => {
|
||||||
// seed items for new guild with guildId
|
// seed items for new guild with guildId
|
||||||
await seedShopItems(guild.id);
|
await seedShopItems(guild.id);
|
||||||
|
|
||||||
|
// Seed spyfall locations for the new guild
|
||||||
|
await seedSpyfallLocations(guild.id);
|
||||||
|
|
||||||
// Register slash commands for the new guild
|
// Register slash commands for the new guild
|
||||||
await registerCommands(guild.id);
|
await registerCommands(guild.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
30
models/Event.js
Normal file
30
models/Event.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
const mongoose = require("mongoose");
|
||||||
|
|
||||||
|
const Participant = require("./Participant");
|
||||||
|
|
||||||
|
const eventSchema = new mongoose.Schema({
|
||||||
|
name: { type: String, required: true, unique: true },
|
||||||
|
description: { type: String, required: true },
|
||||||
|
category: {
|
||||||
|
type: String,
|
||||||
|
enum: ["tournamets", "meeting", "giveaway", "other"],
|
||||||
|
default: "other",
|
||||||
|
},
|
||||||
|
location: { type: String, default: "Online" },
|
||||||
|
startDate: { type: Date, required: true },
|
||||||
|
endDate: { type: Date, required: true },
|
||||||
|
organizerId: { type: String, required: true },
|
||||||
|
participants: [{ type: mongoose.Schema.Types.ObjectId, ref: "Participant" }],
|
||||||
|
recurrence: {
|
||||||
|
type: String,
|
||||||
|
enum: ["none", "daily", "weekly", "monthly"],
|
||||||
|
default: "none",
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: String,
|
||||||
|
enum: ["upcoming", "completed", "cancelled"],
|
||||||
|
default: "upcoming",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model("Event", eventSchema);
|
8
models/Participant.js
Normal file
8
models/Participant.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
const mongoose = require("mongoose");
|
||||||
|
|
||||||
|
const participantSchema = new mongoose.Schema({
|
||||||
|
userId: { type: String, required: true },
|
||||||
|
username: { type: String, required: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model("Participant", participantSchema);
|
18
models/SpyfallGame.js
Normal file
18
models/SpyfallGame.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
const mongoose = require("mongoose");
|
||||||
|
const { v4: uuidv4 } = require("uuid");
|
||||||
|
|
||||||
|
const gameStatusEnum = {
|
||||||
|
values: ["ongoing", "ended"],
|
||||||
|
message: 'Status must be either "ongoing" or "ended"',
|
||||||
|
};
|
||||||
|
|
||||||
|
const spyfallGameSchema = new mongoose.Schema({
|
||||||
|
gameId: { type: String, required: true, unique: true, default: uuidv4 },
|
||||||
|
guildId: { type: String, required: true },
|
||||||
|
location: { type: String, required: true },
|
||||||
|
spy: { type: String, required: true },
|
||||||
|
players: { type: [String], required: true },
|
||||||
|
status: { type: String, enum: gameStatusEnum, default: "ongoing" },
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model("SpyfallGame", spyfallGameSchema);
|
7
models/SpyfallLocation.js
Normal file
7
models/SpyfallLocation.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
const mongoose = require("mongoose");
|
||||||
|
|
||||||
|
const spyfallLocationSchema = new mongoose.Schema({
|
||||||
|
name: { type: String, required: true, unique: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model("SpyfallLocation", spyfallLocationSchema);
|
|
@ -16,9 +16,11 @@
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"html-entities": "^2.5.2",
|
"html-entities": "^2.5.2",
|
||||||
|
"moment": "^2.30.1",
|
||||||
"mongoose": "^8.6.0",
|
"mongoose": "^8.6.0",
|
||||||
"nodemailer": "^6.9.14",
|
"nodemailer": "^6.9.14",
|
||||||
"owoify-js": "^2.0.0",
|
"owoify-js": "^2.0.0",
|
||||||
"puppeteer": "^23.4.1"
|
"puppeteer": "^23.4.1",
|
||||||
|
"uuid": "^11.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
52
utils/seedSpyfallLocations.js
Normal file
52
utils/seedSpyfallLocations.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
const SpyfallLocation = require("../models/SpyfallLocation");
|
||||||
|
|
||||||
|
async function seedSpyfallLocations(guildId) {
|
||||||
|
const locations = [
|
||||||
|
{ name: "Beach" },
|
||||||
|
{ name: "Casino" },
|
||||||
|
{ name: "Circus" },
|
||||||
|
{ name: "Cruise Ship" },
|
||||||
|
{ name: "Hospital" },
|
||||||
|
{ name: "Hotel" },
|
||||||
|
{ name: "Military Base" },
|
||||||
|
{ name: "Movie Studio" },
|
||||||
|
{ name: "Pirate Ship" },
|
||||||
|
{ name: "Polar Station" },
|
||||||
|
{ name: "Police Station" },
|
||||||
|
{ name: "Restaurant" },
|
||||||
|
{ name: "School" },
|
||||||
|
{ name: "Space Station" },
|
||||||
|
{ name: "Submarine" },
|
||||||
|
{ name: "Supermarket" },
|
||||||
|
{ name: "Theater" },
|
||||||
|
{ name: "University" },
|
||||||
|
{ name: "Zoo" },
|
||||||
|
{ name: "Airplane" },
|
||||||
|
{ name: "Bank" },
|
||||||
|
{ name: "Cathedral" },
|
||||||
|
{ name: "Corporate Party" },
|
||||||
|
{ name: "Crusader Army" },
|
||||||
|
{ name: "Day Spa" },
|
||||||
|
{ name: "Embassy" },
|
||||||
|
{ name: "Jail" },
|
||||||
|
{ name: "Museum" },
|
||||||
|
{ name: "Passenger Train" },
|
||||||
|
{ name: "Service Station" },
|
||||||
|
{ name: "Space Station" },
|
||||||
|
{ name: "Subway" },
|
||||||
|
{ name: "The U.N." },
|
||||||
|
{ name: "World Cup Final" },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const location of locations) {
|
||||||
|
await SpyfallLocation.updateOne(
|
||||||
|
{ name: location.name },
|
||||||
|
{ $set: location },
|
||||||
|
{ upsert: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✅ Spyfall Locations seeded for guild: ${guildId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = seedSpyfallLocations;
|
Loading…
Reference in a new issue