Compare commits

...
39 Commits
Author SHA1 Message Date
1tzEmerald e3fa8b6d72 Merge pull request #185 from Emerald-Services/dev
Update close.js
2024-11-10 13:31:17 +00:00
1tzEmerald 5c6a9f7306 Update close.js 2024-11-10 13:30:42 +00:00
1tzEmerald 71e33dbf66 Update close.js 2024-11-10 13:30:27 +00:00
1tzEmerald d468ab1575 Delete Data/Profiles directory 2024-11-10 13:18:50 +00:00
1tzEmerald 74482af471 Merge pull request #184 from Emerald-Services/dev
Dev
2024-11-09 13:04:16 +00:00
1tzEmerald 0b8b092cb9 Update profile.js 2024-11-09 13:03:55 +00:00
1tzEmerald 3cdfaef942 Update profile.js 2024-11-09 13:03:41 +00:00
1tzEmerald fcf441a190 Update close.js 2024-11-09 02:46:35 +00:00
1tzEmerald 5a1c4ad99c Update close.js 2024-11-09 02:46:25 +00:00
1tzEmerald d9cb72f22e Update close.js 2024-11-09 02:44:48 +00:00
1tzEmerald 58eb37124e Update close.js 2024-11-09 02:44:39 +00:00
1tzEmerald afeeda3a8d Merge pull request #183 from Emerald-Services/dev
Dev
2024-11-09 02:38:20 +00:00
1tzEmerald fece90d2bd Update close.js 2024-11-09 02:37:26 +00:00
1tzEmerald 357a85fa3d Update close.js 2024-11-09 02:37:14 +00:00
1tzEmerald f34158caa6 Update close.js 2024-11-09 02:32:52 +00:00
1tzEmerald 1bb343e49a Update ticket.js 2024-11-09 02:32:28 +00:00
1tzEmerald dffa5d4161 Update close.js 2024-11-09 02:32:06 +00:00
1tzEmerald 13590b08a8 Update ticket.js 2024-11-09 01:07:16 +00:00
1tzEmerald 6e14ce209d Merge pull request #182 from Emerald-Services/dev
8.3 Dev -> Release
2024-11-09 00:45:40 +00:00
1tzEmerald 72b8c2247e Merge branch 'release' into dev 2024-11-09 00:45:28 +00:00
1tzEmerald 925504a928 Merge branch 'dev' of https://github.com/Emerald-Services/SupportBot into dev 2024-11-09 00:37:53 +00:00
1tzEmerald 236d67707f 8.3 Update 2024-11-09 00:37:51 +00:00
1tzEmerald 753f432c95 Merge pull request #179 from Emerald-Services/1tzemerald-patch-4
Update package.json
2024-10-11 15:04:37 +01:00
1tzEmerald c824bc51b2 Update package.json 2024-10-11 15:03:47 +01:00
1tzEmerald 39ee540b55 Merge pull request #177 from Emerald-Services/dev
8.2
2024-10-11 15:00:05 +01:00
1tzEmerald 903b214051 Merge branch 'release' into dev 2024-10-11 14:59:50 +01:00
1tzEmerald ac0daf5fe7 8.2 2024-10-10 19:14:45 +01:00
ItsNyoty 80e38a07e9 Merge pull request #175 from Emerald-Services/ItsNyoty-patch-1
Update README.md
2024-09-19 13:06:05 +02:00
ItsNyoty d2598e09f7 Update README.md 2024-09-19 13:05:26 +02:00
ItsNyoty 28aab47150 Update README.md 2024-09-19 13:04:44 +02:00
1tzEmerald 81bda67ae4 Merge pull request #172 from Emerald-Services/1tzemerald-patch-3
Update supportbot.yml
2024-07-30 17:58:41 +01:00
1tzEmerald 25bbd1ff85 Update supportbot.yml 2024-07-30 17:58:01 +01:00
1tzEmerald 164d695a5f Merge pull request #171 from Emerald-Services/1tzemerald-patch-2
Create 0000000000000000.json
2024-07-30 17:57:33 +01:00
1tzEmerald 57e4a3e77c Create 0000000000000000.json 2024-07-30 17:57:12 +01:00
1tzEmerald a6ac532b2b Merge pull request #169 from Emerald-Services/dev
8.0.1 Dev -> Release Branch
2024-07-30 17:47:13 +01:00
1tzEmerald 4f14722e14 8.0.1 Update 2024-07-30 17:45:35 +01:00
1tzEmerald 9a6eece7a1 v8.1 2024-07-09 22:49:49 +01:00
1tzEmerald fd65d0a9a7 8.1-BETA 2024-07-03 13:46:57 +01:00
ItsNyoty 48500fe8a4 Update README.md 2024-07-03 13:42:46 +02:00
41 changed files with 3872 additions and 1408 deletions

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
const Discord = require("discord.js");
const fs = require("fs");
const yaml = require("js-yaml");
const { Command } = require('../Structures/Addon.js'); // Adjust the path as needed
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
module.exports = new Command({
name: "addon",
description: "This is a sample addon!",
options: [],
permissions: ["Administrator"],
async run(interaction) {
let disableCommand = true;
const PingEmbed = new Discord.EmbedBuilder()
.setDescription(
`This is a sample addon.\n\nPong! \`${interaction.client.ws.ping} ms\``
)
.setColor(supportbot.Embed.Colours.General);
interaction.reply({
embeds: [PingEmbed],
});
},
});
-128
View File
@@ -1,128 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
our discord.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
-92
View File
@@ -1,92 +0,0 @@
# Contributing
When contributing to this repository, please first discuss the change you wish to make via issue,
email, or any other method with the owners of this repository before making a change.
Please note we have a code of conduct, please follow it in all your interactions with the project.
## Pull Request Process
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
build.
2. Update the README.md with details of changes to the interface, this includes new environment
variables, exposed ports, useful file locations and container parameters.
3. Increase the version numbers in any examples files and the README.md to the new version that this
Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
do not have permission to do that, you may request the second reviewer to merge it for you.
## Code of Conduct
### Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
### Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
### Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
### Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
### Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
### Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
+217
View File
@@ -0,0 +1,217 @@
const fs = require("fs");
const {
EmbedBuilder,
ApplicationCommandOptionType,
ApplicationCommandType,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
ChannelType,
} = require("discord.js");
const yaml = require("js-yaml");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const Command = require("../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.AddUser.Command,
description: cmdconfig.AddUser.Description,
type: ApplicationCommandType.ChatInput,
options: [
{
name: "user",
description: "The user to add",
type: ApplicationCommandOptionType.User,
required: true,
},
],
permissions: cmdconfig.AddUser.Permission,
async run(interaction) {
const userToAdd = interaction.options.getUser("user");
const ticketChannel = interaction.channel;
const ticketDataPath = "./Data/TicketData.json";
if (
(supportbot.Ticket.TicketType === "threads" && !ticketChannel.isThread()) ||
(supportbot.Ticket.TicketType === "channels" && ticketChannel.type !== ChannelType.GuildText)
) {
const onlyInTicket = new EmbedBuilder()
.setTitle(msgconfig.AddUser.NotInTicket_Title)
.setDescription(msgconfig.AddUser.NotInTicket_Description)
.setColor(supportbot.Embed.Colours.Error);
return interaction.reply({
embeds: [onlyInTicket],
ephemeral: true,
});
}
const ticketInviteEmbed = new EmbedBuilder()
.setTitle(msgconfig.AddUser.Invite_Title)
.setDescription(msgconfig.AddUser.Invite_Description.replace("%username%", userToAdd.username))
.setColor(supportbot.Embed.Colours.General);
await interaction.reply({
embeds: [ticketInviteEmbed],
ephemeral: true,
});
try {
const dmEmbed = new EmbedBuilder()
.setTitle(msgconfig.AddUser.DM_Title)
.setDescription(msgconfig.AddUser.DM_Description.replace("%channelname%", ticketChannel.name))
.setColor(supportbot.Embed.Colours.General);
const acceptButton = new ButtonBuilder()
.setCustomId("accept_invite")
.setLabel(msgconfig.AddUser.Accept_Label)
.setEmoji("✅")
.setStyle(ButtonStyle.Primary);
const rejectButton = new ButtonBuilder()
.setCustomId("reject_invite")
.setLabel(msgconfig.AddUser.Reject_Label)
.setEmoji("❌")
.setStyle(ButtonStyle.Secondary);
const row = new ActionRowBuilder().addComponents(acceptButton, rejectButton);
const dmMessage = await userToAdd.send({
embeds: [dmEmbed],
components: [row],
});
const filter = (i) => i.user.id === userToAdd.id;
const collector = dmMessage.createMessageComponentCollector({
filter,
time: 60000,
});
collector.on("collect", async (i) => {
if (i.customId === "accept_invite") {
try {
// Add user to thread or channel based on TicketType
if (supportbot.Ticket.TicketType === "threads") {
await ticketChannel.members.add(userToAdd.id); // For threads
} else if (supportbot.Ticket.TicketType === "channels") {
await ticketChannel.permissionOverwrites.create(userToAdd.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
}); // For channels
}
const ticketData = JSON.parse(fs.readFileSync(ticketDataPath, "utf8"));
const ticketIndex = ticketData.tickets.findIndex((t) => t.id === ticketChannel.id);
if (ticketIndex !== -1) {
ticketData.tickets[ticketIndex].subUsers.push(userToAdd.id);
fs.writeFileSync(ticketDataPath, JSON.stringify(ticketData, null, 4));
}
const acceptedEmbed = new EmbedBuilder()
.setTitle(msgconfig.AddUser.Accepted_Title)
.setDescription(msgconfig.AddUser.Accepted_Message)
.setColor(supportbot.Embed.Colours.Success);
await i.update({
embeds: [acceptedEmbed],
components: [],
});
// Send the added user ticket message in the ticket channel
const addeduserTicket = new EmbedBuilder()
.setTitle(msgconfig.AddUser.Added_Title)
.setDescription(msgconfig.AddUser.Added_Description.replace('%username%', userToAdd.username))
.setColor(supportbot.Embed.Colours.General);
await ticketChannel.send({
embeds: [addeduserTicket],
});
const addedToTicketEmbed = new EmbedBuilder()
.setTitle(msgconfig.AddUser.AddedToTicket_Title)
.setDescription(msgconfig.AddUser.AddedToTicket_Description.replace(
"%channel_link%",
`[${ticketChannel.name}](https://discord.com/channels/${ticketChannel.guild.id}/${ticketChannel.id})`
))
.setColor(supportbot.Embed.Colours.General);
const ticketLinkButton = new ButtonBuilder()
.setLabel(msgconfig.AddUser.ViewTicket_Label)
.setURL(`https://discord.com/channels/${ticketChannel.guild.id}/${ticketChannel.id}`)
.setStyle(ButtonStyle.Link);
await userToAdd.send({
embeds: [addedToTicketEmbed],
components: [new ActionRowBuilder().addComponents(ticketLinkButton)],
});
} catch (err) {
console.error("Error adding user to the ticket:", err);
const errorEmbed = new EmbedBuilder()
.setTitle(msgconfig.AddUser.Error_Title)
.setDescription(msgconfig.AddUser.Error_Adding)
.setColor(supportbot.Embed.Colours.Error);
await i.update({
embeds: [errorEmbed],
components: [],
});
}
} else {
const declinedEmbed = new EmbedBuilder()
.setTitle(msgconfig.AddUser.Declined_Title)
.setDescription(msgconfig.AddUser.Declined_Message)
.setColor(supportbot.Embed.Colours.Error);
await i.update({
embeds: [declinedEmbed],
components: [],
});
}
});
collector.on("end", (collected) => {
if (collected.size === 0) {
userToAdd.send({
embeds: [
new EmbedBuilder()
.setTitle(msgconfig.AddUser.NoResponse_Title)
.setDescription(msgconfig.AddUser.NoResponse_Message)
.setColor(supportbot.Embed.Colours.Warning),
],
});
}
});
} catch (error) {
if (error.code === 50007) {
const errorEmbed = new EmbedBuilder()
.setTitle(msgconfig.AddUser.DM_Error_Title)
.setDescription(msgconfig.AddUser.DM_Error_Description.replace("%username%", userToAdd.username))
.setColor(supportbot.Embed.Colours.Error);
await ticketChannel.send({
embeds: [errorEmbed],
});
} else {
console.error("Error sending DM:", error);
const unexpectedErrorEmbed = new EmbedBuilder()
.setTitle(msgconfig.AddUser.Unexpected_Error_Title)
.setDescription(msgconfig.AddUser.Unexpected_Error_Description.replace("%error%", error.message))
.setColor(supportbot.Embed.Colours.Error);
await ticketChannel.send({
embeds: [unexpectedErrorEmbed],
});
}
}
},
});
+344 -253
View File
@@ -2,15 +2,9 @@ const fs = require("fs");
const Discord = require("discord.js"); const Discord = require("discord.js");
const yaml = require("js-yaml"); const yaml = require("js-yaml");
const supportbot = yaml.load( const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
fs.readFileSync("./Configs/supportbot.yml", "utf8") const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
); const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const cmdconfig = yaml.load(
fs.readFileSync("./Configs/commands.yml", "utf8")
);
const msgconfig = yaml.load(
fs.readFileSync("./Configs/messages.yml", "utf8")
);
const Command = require("../Structures/Command.js"); const Command = require("../Structures/Command.js");
@@ -23,7 +17,7 @@ module.exports = new Command({
name: "reason", name: "reason",
description: "Ticket Close Reason", description: "Ticket Close Reason",
type: Discord.ApplicationCommandOptionType.String, type: Discord.ApplicationCommandOptionType.String,
}, }
], ],
permissions: cmdconfig.CloseTicket.Permission, permissions: cmdconfig.CloseTicket.Permission,
@@ -34,29 +28,28 @@ module.exports = new Command({
let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild); let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild); let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!SupportStaff || !Admin) if (!SupportStaff || !Admin) {
return interaction.reply( return interaction.reply("Some roles seem to be missing! Please check for errors when starting the bot.");
"Some roles seem to be missing!\nPlease check for errors when starting the bot." }
);
const NoPerms = new Discord.EmbedBuilder() const NoPerms = new Discord.EmbedBuilder()
.setTitle("Invalid Permissions!") .setTitle("Invalid Permissions!")
.setDescription( .setDescription(`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``)
`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``
)
.setColor(supportbot.Embed.Colours.Warn); .setColor(supportbot.Embed.Colours.Warn);
if ( if (!interaction.member.roles.cache.has(SupportStaff.id) && !interaction.member.roles.cache.has(Admin.id)) {
!interaction.member.roles.cache.has(SupportStaff.id) &&
!interaction.member.roles.cache.has(Admin.id)
)
return interaction.reply({ embeds: [NoPerms] }); return interaction.reply({ embeds: [NoPerms] });
}
} }
if (interaction.channel.type !== Discord.ChannelType.PrivateThread) { const isThread = interaction.channel.type === Discord.ChannelType.PrivateThread;
if (
(supportbot.Ticket.TicketType === "threads" && !isThread) ||
(supportbot.Ticket.TicketType === "channels" && interaction.channel.type !== Discord.ChannelType.GuildText)
) {
const NotTicketChannel = new Discord.EmbedBuilder() const NotTicketChannel = new Discord.EmbedBuilder()
.setTitle("Invalid Channel!") .setTitle("Invalid Channel!")
.setDescription("This command can only be used in a ticket thread.") .setDescription(`This command can only be used in a ${supportbot.Ticket.TicketType === "threads" ? "ticket thread" : "ticket channel"}.`)
.setColor(supportbot.Embed.Colours.Warn); .setColor(supportbot.Embed.Colours.Warn);
return interaction.reply({ embeds: [NotTicketChannel], ephemeral: true }); return interaction.reply({ embeds: [NotTicketChannel], ephemeral: true });
@@ -64,7 +57,14 @@ module.exports = new Command({
await interaction.deferReply(); await interaction.deferReply();
let tickets = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8")); let tickets;
try {
tickets = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
} catch (err) {
console.error("Error reading ticket data file:", err);
return interaction.followUp({ content: "There was an error loading ticket data." });
}
let TicketData = tickets.tickets.findIndex((t) => t.id === interaction.channel.id); let TicketData = tickets.tickets.findIndex((t) => t.id === interaction.channel.id);
let ticket = tickets.tickets[TicketData]; let ticket = tickets.tickets[TicketData];
@@ -77,259 +77,350 @@ module.exports = new Command({
} }
let reason = interaction.options?.getString("reason") || "No Reason Provided."; let reason = interaction.options?.getString("reason") || "No Reason Provided.";
const ticketUserId = ticket.user;
const CloseButton = new Discord.ButtonBuilder() if (supportbot.Ticket.ReviewSystem.Enabled) {
.setCustomId("confirmCloseTicket") const rating = await collectReviewRating(interaction, ticketUserId);
.setLabel(supportbot.Ticket.Close.Confirmation_Button) const comment = await collectReviewComment(interaction, ticketUserId);
.setStyle(Discord.ButtonStyle.Danger);
const row = new Discord.ActionRowBuilder().addComponents(CloseButton); const reviewChannel = await getChannel(supportbot.Ticket.ReviewSystem.Channel, interaction.guild);
const reviewer = supportbot.Ticket.ClaimTickets ? `<@${ticket.claimedBy}>` : null;
const CloseTicketRequest = new Discord.EmbedBuilder() const reviewEmbed = new Discord.EmbedBuilder()
.setTitle(`**${supportbot.Ticket.Close.Title}**`) .setTitle(msgconfig.ReviewSystem.ReviewEmbed.Title)
.setDescription( .addFields(
`${msgconfig.Ticket.ConfirmClose}` {
) name: msgconfig.ReviewSystem.ReviewEmbed.RatingTitle,
.setColor(supportbot.Embed.Colours.General); value: `${msgconfig.ReviewSystem.ReviewEmbed.ReviewEmoji.repeat(rating)}`,
inline: false
},
{
name: msgconfig.ReviewSystem.ReviewEmbed.CommentTitle,
value: `\`\`\`${comment}\`\`\``,
inline: false
}
)
.setColor(msgconfig.ReviewSystem.ReviewEmbed.Color);
await interaction.followUp({ if (supportbot.Ticket.ClaimTickets.Enabled) {
embeds: [CloseTicketRequest], reviewEmbed.setDescription(
components: [row], `**${msgconfig.ReviewSystem.ReviewEmbed.ReviewedStaffTitle}** ${reviewer}\n**${msgconfig.ReviewSystem.ReviewEmbed.ReviewedByTitle}** <@${ticketUserId}>` || `N/A`
}); );
} else {
reviewEmbed.setDescription(
`**${msgconfig.ReviewSystem.ReviewEmbed.ReviewedByTitle}** <@${ticketUserId}>`
);
}
const filter = (btnInteraction) => btnInteraction.customId === "confirmCloseTicket" && btnInteraction.user.id === interaction.user.id; if (reviewChannel) {
await reviewChannel.send({ embeds: [reviewEmbed] });
}
interaction.channel.awaitMessageComponent({ filter, time: 20000 }) if (!interaction.replied && !interaction.deferred) {
.then(async (btnInteraction) => { await interaction.followUp({ embeds: [reviewEmbed] });
await handleCloseTicket(interaction, reason, ticket, TicketData); }
await btnInteraction.reply({ content: "Ticket successfully closed.", ephemeral: true });
}) await handleCloseTicket(interaction, reason, ticket, TicketData);
.catch((err) => { } else {
interaction.followUp(`${msgconfig.Ticket.CloseTimeout}`); await handleCloseTicket(interaction, reason, ticket, TicketData);
console.error(err); }
});
}, },
}); });
async function collectReviewRating(interaction, ticketUserId) {
const reviewPrompt = new Discord.EmbedBuilder()
.setTitle(msgconfig.ReviewSystem.Rate.Title)
.setDescription(`${msgconfig.ReviewSystem.Rate.Description}`)
.setColor(supportbot.Embed.Colours.General);
const stars = new Discord.ActionRowBuilder().addComponents(
new Discord.StringSelectMenuBuilder()
.setCustomId("starRating")
.setPlaceholder("Select a rating")
.addOptions([
{ label: msgconfig.ReviewSystem.Stars.One, value: "1" },
{ label: msgconfig.ReviewSystem.Stars.Two, value: "2" },
{ label: msgconfig.ReviewSystem.Stars.Three, value: "3" },
{ label: msgconfig.ReviewSystem.Stars.Four, value: "4" },
{ label: msgconfig.ReviewSystem.Stars.Five, value: "5" },
])
);
await interaction.followUp({
content: `<@${ticketUserId}>`,
embeds: [reviewPrompt],
components: [stars],
ephemeral: true,
});
const starRating = await interaction.channel.awaitMessageComponent({
componentType: Discord.ComponentType.StringSelect,
filter: (i) => i.customId === "starRating" && i.user.id === ticketUserId,
});
await starRating.deferUpdate();
return starRating.values[0];
}
async function collectReviewComment(interaction, ticketUserId) {
const commentPrompt = new Discord.EmbedBuilder()
.setTitle(msgconfig.ReviewSystem.Comment.Title)
.setDescription(`${msgconfig.ReviewSystem.Comment.Description}`)
.setColor(supportbot.Embed.Colours.General);
await interaction.followUp({
content: `<@${ticketUserId}>`,
embeds: [commentPrompt],
ephemeral: true,
});
const filter = (response) => response.author.id === ticketUserId;
const commentCollection = await interaction.channel.awaitMessages({ filter, max: 1 });
const comment = commentCollection.first()?.content;
return comment && comment.toLowerCase() !== "no" ? comment : "No Comment Provided";
}
async function handleCloseTicket(interaction, reason, ticket, TicketData) { async function handleCloseTicket(interaction, reason, ticket, TicketData) {
const { getChannel } = interaction.client; const { getChannel } = interaction.client;
let tickets = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8")); let tickets;
let tUser = interaction.client.users.cache.get(ticket.user); try {
let transcriptChannel = await getChannel(supportbot.Ticket.Log.TranscriptLog, interaction.guild); tickets = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
let logChannel = await getChannel(supportbot.Ticket.Log.TicketLog, interaction.guild); } catch (err) {
console.error("Error reading ticket data:", err);
return interaction.followUp("Failed to load ticket data.");
}
if (!transcriptChannel || !logChannel) let tUser = interaction.client.users.cache.get(ticket.user);
return interaction.followUp("Some Channels seem to be missing!"); let transcriptChannel = await getChannel(supportbot.Ticket.Log.TicketDataLog, interaction.guild);
if (!transcriptChannel) {
console.log("Transcript channel missing or inaccessible");
return interaction.followUp("Error: Transcript log channel is missing or bot lacks permission.");
}
try { try {
tickets.tickets[TicketData].open = false; // Fetch all messages from the channel
fs.writeFileSync( let allMessages = [];
"./Data/TicketData.json", let lastId = null;
JSON.stringify(tickets, null, 4),
(err) => { while (true) {
if (err) console.error(err); const options = { limit: 100 };
if (lastId) {
options.before = lastId;
} }
);
const messages = await interaction.channel.messages.fetch(options);
if (messages.size === 0) break;
allMessages = [...allMessages, ...messages.values()];
lastId = messages.last().id;
if (messages.size < 100) break;
}
// Sort messages by timestamp
allMessages.sort((a, b) => a.createdTimestamp - b.createdTimestamp);
// Create transcript data
const transcriptData = allMessages.map(msg => ({
content: msg.content || "No content",
username: msg.author.username,
userId: msg.author.id,
avatar: msg.author.displayAvatarURL(),
timestamp: msg.createdAt.toISOString(),
attachments: Array.from(msg.attachments.values()).map(att => ({
url: att.url,
name: att.name
})),
embeds: msg.embeds.map(embed => ({
title: embed.title,
description: embed.description,
fields: embed.fields
}))
}));
// Update ticket status
tickets.tickets[TicketData].open = false;
tickets.tickets[TicketData].messages = transcriptData;
fs.writeFileSync("./Data/TicketData.json", JSON.stringify(tickets, null, 4));
const transcriptEmbed = new Discord.EmbedBuilder() const transcriptEmbed = new Discord.EmbedBuilder()
.setTitle(supportbot.Ticket.Log.TranscriptTitle) .setTitle(msgconfig.TicketLog.Title)
.setColor(supportbot.Embed.Colours.General) .setColor(msgconfig.TicketLog.Colour)
.setFooter({ .setFooter({ text: supportbot.Embed.Footer, iconURL: interaction.user.displayAvatarURL() })
text: supportbot.Embed.Footer, .setDescription(
iconURL: interaction.user.displayAvatarURL(), `> **Ticket:** ${interaction.channel.name} (\`${interaction.channel.id}\`)\n` +
}) `> **User:** ${tUser?.tag || "Unknown User"} (\`${tUser?.id || ticket.user}\`)\n` +
.addFields( `> **Closed by:** <@${interaction.user.id}>\n` +
{ name: "Ticket", value: `${interaction.channel.name} (${interaction.channel.id})` }, `> **Message Count:** ${transcriptData.length}`
{ name: "User", value: `${tUser?.username || "N/A"}#${tUser?.discriminator || "N/A"} (${tUser?.id || ticket.user})` }, )
{ name: "Closed By", value: interaction.user.tag }, .addFields({ name: "Reason", value: `\`\`\`${reason}\`\`\``, inline: true });
{ name: "Reason", value: reason }
);
const logEmbed = new Discord.EmbedBuilder() const html = createTranscriptHTML(
.setTitle(supportbot.Ticket.Log.TicketLog_Title) {
.setColor(supportbot.Embed.Colours.General) id: interaction.channel.id,
.setFooter({ messages: transcriptData,
text: supportbot.Embed.Footer, name: interaction.channel.name
iconURL: interaction.user.displayAvatarURL(), },
}) reason
.addFields(
{ name: "Ticket", value: `${interaction.channel.name} (${interaction.channel.id})` },
{ name: "User", value: `${tUser?.username || "N/A"}#${tUser?.discriminator || "N/A"} (${tUser?.id || ticket.user})` },
{ name: "Closed By", value: interaction.user.tag },
{ name: "Reason", value: reason }
);
let msgs = await interaction.channel.messages.fetch({ limit: 100 });
let html = `
<html>
<head>
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
<style>
body {
background-color: #1a1a1a;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: white;
margin: 0;
padding: 0;
height: 100%;
display: flex;
flex-direction: column;
}
.container {
backdrop-filter: blur(10px);
background-color: rgba(0, 128, 0, 0.3);
border-radius: 15px;
padding: 20px;
margin: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
flex-grow: 1;
display: flex;
flex-direction: column;
max-width: 1000px;
width: 100%;
margin: auto;
}
.message {
display: flex;
align-items: center;
margin-bottom: 10px;
padding: 10px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 10px;
}
.content {
flex: 1;
}
.username {
font-weight: bold;
}
.timestamp {
font-size: 0.8em;
color: #bbb;
}
.download-button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin-top: 20px;
border-radius: 5px;
}
</style>
</head>
<body>
<div class="container">
<h1 class="text-2xl font-bold mb-4">Ticket Transcript</h1>
<p><strong>Server Name:</strong> ${interaction.guild.name}</p>
<p><strong>Ticket:</strong> ${interaction.channel.name}</p>
<p><strong>Messages:</strong> ${msgs.size} Messages</p>
<div class="mt-4">
`;
msgs = msgs.sort((a, b) => a.createdTimestamp - b.createdTimestamp);
msgs.forEach((msg) => {
if (msg.content) {
html += `
<div class="message">
<img class="avatar" src="${msg.author.displayAvatarURL()}" alt="Avatar">
<div class="content">
<p class="username">${msg.author.tag}</p>
<p class="timestamp">${msg.createdAt.toLocaleString()}</p>
<p>${convertMarkdownToHTML(msg.content)}</p>
</div>
</div>
`;
}
});
const fileName = `${interaction.channel.name}.html`;
html += `
</div>
<a href="${fileName}" download class="download-button">Download Transcript</a>
</div>
</body>
</html>
`;
if (!supportbot.Ticket.Log.DisableTicketLogChannel) {
await logChannel.send({ embeds: [logEmbed] }).catch((err) => {
console.error(err);
});
}
let file = new Discord.AttachmentBuilder(
Buffer.from(html),
{ name: fileName }
); );
await transcriptChannel const fileName = `${interaction.channel.id}-transcript.html`;
.send({ embeds: [transcriptEmbed], files: [file] }) fs.writeFileSync(`./Data/Transcripts/${fileName}`, html);
.catch((err) => {
console.error(err);
});
if (supportbot.Ticket.DMTranscripts) { await transcriptChannel.send({
await tUser embeds: [transcriptEmbed],
?.send({ embeds: [transcriptEmbed], files: [file] }) files: [{
.catch((err) => { attachment: `./Data/Transcripts/${fileName}`,
console.error(err); name: `SPOILER_${fileName}`,
}); spoiler: true
}]
if (ticket.subUsers) {
ticket.subUsers.forEach(async (subUser) => {
await interaction.client.users.cache
.get(subUser)
?.send({ embeds: [transcriptEmbed], files: [file] });
});
}
}
await interaction.channel.delete().catch((error) => {
console.error(error);
if (error.code !== Discord.Constants.APIErrors.UNKNOWN_CHANNEL) {
console.error("Failed to delete the interaction:", error);
}
}); });
} catch (error) {
console.error(error); if (interaction.channel.type === Discord.ChannelType.GuildText ||
interaction.channel.type === Discord.ChannelType.PrivateThread) {
await interaction.channel.delete();
}
} catch (err) {
console.error("Error handling ticket close:", err);
interaction.followUp("An error occurred while closing the ticket.");
} }
} }
function convertMarkdownToHTML(text) { function parseMarkdown(content) {
// Convert **bold** to <strong>bold</strong> // Convert **bold** text to <strong> tags
text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>'); content = content.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
// Convert *italic* to <em>italic</em> // Convert _italic_ text to <em> tags
text = text.replace(/\*(.*?)\*/g, '<em>$1</em>'); content = content.replace(/_(.*?)_/g, '<em>$1</em>');
// Convert __underline__ to <u>underline</u> // Convert inline `code` to <code> tags
text = text.replace(/__(.*?)__/g, '<u>$1</u>'); content = content.replace(/`(.*?)`/g, '<code>$1</code>');
// Convert ~~strikethrough~~ to <s>strikethrough</s> // Convert [text](link) to <a> tags
text = text.replace(/~~(.*?)~~/g, '<s>$1</s>'); content = content.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank">$1</a>');
// Convert inline code `code` to <code>code</code> // Convert multiline code blocks ```code``` to <pre><code> tags
text = text.replace(/`(.*?)`/g, '<code>$1</code>'); content = content.replace(/```(.*?)```/gs, '<pre><code>$1</code></pre>');
// Convert blockquotes > quote to <blockquote>quote</blockquote> return content;
text = text.replace(/^> (.*?$)/gm, '<blockquote>$1</blockquote>'); }
// Convert multiline code blocks ```code``` to <pre><code>code</code></pre> function createTranscriptHTML(ticket, reason) {
text = text.replace(/```(.*?)```/gs, '<pre><code>$1</code></pre>'); return `
<!DOCTYPE html>
// Convert line breaks to <br> <html>
text = text.replace(/\n/g, '<br>'); <head>
<meta charset="UTF-8">
return text; <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
<style>
body {
background-color: #1a1a1a;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: white;
margin: 0;
padding: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 15px;
padding: 20px;
}
.message {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 15px;
margin-bottom: 15px;
}
.message-header {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 15px;
}
.username {
font-weight: bold;
color: #4CAF50;
}
.timestamp {
margin-left: auto;
color: #888;
font-size: 0.9em;
}
.content {
word-break: break-word;
}
.embed {
border-left: 4px solid #4CAF50;
padding-left: 10px;
margin: 10px 0;
background-color: rgba(0, 0, 0, 0.2);
}
.attachment {
display: inline-block;
background-color: rgba(76, 175, 80, 0.1);
border: 1px solid #4CAF50;
border-radius: 5px;
padding: 5px 10px;
margin: 5px 0;
color: #4CAF50;
text-decoration: none;
}
</style>
</head>
<body>
<div class="container">
<div class="mb-6">
<h1 class="text-3xl font-bold mb-4">Ticket Transcript</h1>
<p><strong>Channel:</strong> ${ticket.name}</p>
<p><strong>Ticket ID:</strong> ${ticket.id}</p>
<p><strong>Message Count:</strong> ${ticket.messages.length}</p>
<p><strong>Close Reason:</strong> ${reason}</p>
</div>
<div class="messages">
${ticket.messages.map(msg => `
<div class="message">
<div class="message-header">
<img src="${msg.avatar}" alt="Avatar" class="avatar">
<span class="username">${msg.username}</span>
<span class="timestamp">${new Date(msg.timestamp).toLocaleString()}</span>
</div>
<div class="content">
${parseMarkdown(msg.content)}
${msg.embeds.map(embed => `
<div class="embed">
${embed.title ? `<div class="font-bold">${embed.title}</div>` : ''}
${embed.description ? `<div>${embed.description}</div>` : ''}
${embed.fields.map(field => `
<div class="mt-2">
<strong>${field.name}:</strong>
<div>${field.value}</div>
</div>
`).join('')}
</div>
`).join('')}
${msg.attachments.map(att => `
<a href="${att.url}" class="attachment" target="_blank">
📎 ${att.name}
</a>
`).join('')}
</div>
</div>
`).join('')}
</div>
</div>
</body>
</html>
`;
} }
+450 -138
View File
@@ -1,167 +1,479 @@
// File path: ./commands/embed.js const {
ApplicationCommandType,
ApplicationCommandOptionType,
ActionRowBuilder,
ButtonBuilder,
EmbedBuilder,
StringSelectMenuBuilder,
ModalBuilder,
TextInputBuilder,
TextInputStyle,
ButtonStyle,
ComponentType
} = require('discord.js');
const Command = require('../Structures/Command.js');
const fs = require('fs');
const yaml = require('js-yaml');
const fs = require("fs"); const supportbot = yaml.load(fs.readFileSync('./Configs/supportbot.yml', 'utf8'));
const Discord = require("discord.js"); const cmdconfig = yaml.load(fs.readFileSync('./Configs/commands.yml', 'utf8'));
const yaml = require("js-yaml"); const msgconfig = yaml.load(fs.readFileSync('./Configs/messages.yml', 'utf8'));
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8")); // Helper function to validate and convert hex color
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8")); function parseColor(color) {
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8")); if (!color) return 0x5865F2;
if (typeof color === 'number') return color;
const Command = require("../Structures/Command.js"); if (typeof color !== 'string') return 0x5865F2;
color = color.replace('#', '');
if (/^[0-9A-F]{6}$/i.test(color)) {
return parseInt(color, 16);
}
return 0x5865F2;
}
module.exports = new Command({ module.exports = new Command({
name: cmdconfig.Embed.Command, name: cmdconfig.Embed.Command,
description: cmdconfig.Embed.Description, description: cmdconfig.Embed.Description,
type: Discord.ApplicationCommandType.ChatInput, type: ApplicationCommandType.ChatInput,
options: [ options: [
{ {
name: "title", name: 'channel',
description: "Embed Title", description: 'Select channel to send the message',
type: Discord.ApplicationCommandOptionType.String, type: ApplicationCommandOptionType.Channel,
required: true, required: true
},
{
name: "message",
description: "Embed Message",
type: Discord.ApplicationCommandOptionType.String,
required: true,
},
{
name: "color",
description: "Embed HEX Color",
type: Discord.ApplicationCommandOptionType.String,
required: false,
},
{
name: "fields",
description: "Embed Fields (title|content;title|content;...)",
type: Discord.ApplicationCommandOptionType.String,
required: false,
},
{
name: "thumbnail",
description: "Embed Thumbnail URL",
type: Discord.ApplicationCommandOptionType.String,
required: false,
},
{
name: "image",
description: "Embed Image URL",
type: Discord.ApplicationCommandOptionType.String,
required: false,
},
{
name: "footer",
description: "Embed Footer Text",
type: Discord.ApplicationCommandOptionType.String,
required: false,
},
{
name: "footericon",
description: "Embed Footer Icon URL",
type: Discord.ApplicationCommandOptionType.String,
required: false,
},
{
name: "author",
description: "Embed Author Name",
type: Discord.ApplicationCommandOptionType.String,
required: false,
},
{
name: "authoricon",
description: "Embed Author Icon URL",
type: Discord.ApplicationCommandOptionType.String,
required: false,
},
{
name: "authorurl",
description: "Embed Author URL",
type: Discord.ApplicationCommandOptionType.String,
required: false,
},
{
name: "timestamp",
description: "Include Timestamp",
type: Discord.ApplicationCommandOptionType.Boolean,
required: false,
},
{
name: "inline",
description: "Make Fields Inline",
type: Discord.ApplicationCommandOptionType.Boolean,
required: false,
} }
], ],
permissions: cmdconfig.Embed.Permission, permissions: cmdconfig.Embed.Permission,
async run(interaction) { async run(interaction) {
// Permission check
const { getRole } = interaction.client; const { getRole } = interaction.client;
let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild); let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild); let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!SupportStaff || !Admin) { if (!SupportStaff || !Admin) {
return interaction.reply("Some roles seem to be missing!\nPlease check for errors when starting the bot."); return interaction.reply('Some roles seem to be missing!\nPlease check for errors when starting the bot.');
} }
const NoPerms = new Discord.EmbedBuilder() if (!interaction.member.roles.cache.has(SupportStaff.id) &&
.setTitle("Invalid Permissions!") !interaction.member.roles.cache.has(Admin.id)) {
.setDescription(`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``) const NoPerms = new EmbedBuilder()
.setColor(supportbot.Embed.Colours.Warn); .setTitle('Invalid Permissions!')
.setDescription(`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``)
.setColor(0xFF0000);
return interaction.reply({ embeds: [NoPerms], ephemeral: true });
}
if ( const channel = interaction.options.getChannel('channel');
interaction.member.roles.cache.has(SupportStaff.id) ||
interaction.member.roles.cache.has(Admin.id) // Initial message state
) { const messageData = {
const EmbedTitle = interaction.options.getString("title").replace(/\\n/g, '\n'); type: 'embed',
const EmbedSubject = interaction.options.getString("message").replace(/\\n/g, '\n'); title: 'New Embed',
const GeneralColour = interaction.options.getString("color") || supportbot.Embed.Colours.Default; description: 'Click the buttons below to edit this embed',
const EmbedThumbnail = interaction.options.getString("thumbnail"); color: 0x5865F2,
const EmbedImage = interaction.options.getString("image"); fields: [],
const EmbedFields = interaction.options.getString("fields"); footer: null,
const EmbedFooter = interaction.options.getString("footer")?.replace(/\\n/g, '\n'); thumbnail: null,
const EmbedFooterIcon = interaction.options.getString("footericon"); image: null,
const EmbedAuthor = interaction.options.getString("author")?.replace(/\\n/g, '\n'); timestamp: false
const EmbedAuthorIcon = interaction.options.getString("authoricon"); };
const EmbedAuthorURL = interaction.options.getString("authorurl");
const EmbedTimestamp = interaction.options.getBoolean("timestamp");
const InlineFields = interaction.options.getBoolean("inline");
const EmbedMsg = new Discord.EmbedBuilder() // Create initial embed
.setTitle(EmbedTitle) function createEmbed() {
.setDescription(EmbedSubject) const embed = new EmbedBuilder()
.setColor(GeneralColour) .setTitle(messageData.title)
.setThumbnail(EmbedThumbnail) .setDescription(messageData.description)
.setImage(EmbedImage); .setColor(messageData.color);
if (EmbedFields) { if (messageData.fields.length > 0) {
const fieldsArray = EmbedFields.split(';'); embed.addFields(messageData.fields);
fieldsArray.forEach(field => { }
const [title, value] = field.split('|'); if (messageData.footer) {
if (title && value) { embed.setFooter({ text: messageData.footer });
EmbedMsg.addFields({ name: title.replace(/\\n/g, '\n'), value: value.replace(/\\n/g, '\n'), inline: InlineFields || false }); }
} if (messageData.thumbnail) {
embed.setThumbnail(messageData.thumbnail);
}
if (messageData.image) {
embed.setImage(messageData.image);
}
if (messageData.timestamp) {
embed.setTimestamp();
}
return embed;
}
// Create type selector
const typeRow = new ActionRowBuilder()
.addComponents(
new StringSelectMenuBuilder()
.setCustomId('message_type')
.setPlaceholder('Select message type')
.addOptions([
{
label: 'Embed Message',
description: 'Send as an embedded message',
value: 'embed'
},
{
label: 'Text Message',
description: 'Send as a plain text message',
value: 'text'
}
])
);
// Create button rows function
function createButtons(messageType) {
const buttons = [];
if (messageType === 'embed') {
// Only show these buttons for embed mode
const row1 = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('edit_title')
.setLabel('Edit Title')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('edit_description')
.setLabel('Edit Description')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('edit_color')
.setLabel('Edit Color')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('add_field')
.setLabel('Add Field')
.setStyle(ButtonStyle.Secondary)
);
const row2 = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('edit_footer')
.setLabel('Edit Footer')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('edit_image')
.setLabel('Add Image')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('edit_thumbnail')
.setLabel('Add Thumbnail')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('toggle_timestamp')
.setLabel('Toggle Timestamp')
.setStyle(ButtonStyle.Secondary)
);
buttons.push(row1, row2);
} else {
// Text mode only shows edit text button
const textRow = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('edit_description')
.setLabel('Edit Text')
.setStyle(ButtonStyle.Secondary)
);
buttons.push(textRow);
}
// Send button always shows
const sendRow = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('send_message')
.setLabel('Send')
.setStyle(ButtonStyle.Success)
);
buttons.push(sendRow);
return buttons;
}
// Function to update the message preview
async function updatePreview(i) {
if (messageData.type === 'embed') {
await i.update({
content: 'Customize your message:',
embeds: [createEmbed()],
components: [typeRow, ...createButtons('embed')]
});
} else {
// For text mode, just show the text directly
await i.update({
content: messageData.description,
embeds: [],
components: [typeRow, ...createButtons('text')]
});
}
}
// Send initial message
const message = await interaction.reply({
content: messageData.type === 'text' ? messageData.description : 'Customize your message:',
components: [typeRow, ...createButtons(messageData.type)],
embeds: messageData.type === 'embed' ? [createEmbed()] : [],
ephemeral: true
});
// Create collector for interactions
const collector = message.createMessageComponentCollector({
time: 900000
});
collector.on('collect', async (i) => {
if (i.user.id !== interaction.user.id) {
return i.reply({
content: 'You cannot edit this message.',
ephemeral: true
}); });
} }
if (EmbedFooter) { switch (i.customId) {
EmbedMsg.setFooter({ text: EmbedFooter, iconURL: EmbedFooterIcon }); case 'message_type':
messageData.type = i.values[0];
await updatePreview(i);
break;
case 'edit_description':
const descModal = new ModalBuilder()
.setCustomId('desc_modal')
.setTitle(messageData.type === 'embed' ? 'Edit Description' : 'Edit Text');
descModal.addComponents(
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('desc_input')
.setLabel(messageData.type === 'embed' ? 'Description' : 'Message Text')
.setStyle(TextInputStyle.Paragraph)
.setValue(messageData.description)
.setMaxLength(4000)
)
);
await i.showModal(descModal);
break;
case 'edit_title':
const titleModal = new ModalBuilder()
.setCustomId('title_modal')
.setTitle('Edit Title');
titleModal.addComponents(
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('title_input')
.setLabel('Title')
.setStyle(TextInputStyle.Short)
.setValue(messageData.title)
.setMaxLength(256)
)
);
await i.showModal(titleModal);
break;
case 'edit_color':
const colorModal = new ModalBuilder()
.setCustomId('color_modal')
.setTitle('Edit Color');
colorModal.addComponents(
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('color_input')
.setLabel('Color (HEX)')
.setStyle(TextInputStyle.Short)
.setPlaceholder('#FF0000')
.setMinLength(6)
.setMaxLength(7)
.setValue(messageData.color.toString(16).padStart(6, '0'))
)
);
await i.showModal(colorModal);
break;
case 'add_field':
const fieldModal = new ModalBuilder()
.setCustomId('field_modal')
.setTitle('Add Field');
fieldModal.addComponents(
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('field_name')
.setLabel('Field Name')
.setStyle(TextInputStyle.Short)
.setMaxLength(256)
),
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('field_value')
.setLabel('Field Value')
.setStyle(TextInputStyle.Paragraph)
.setMaxLength(1024)
),
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('field_inline')
.setLabel('Inline? (true/false)')
.setStyle(TextInputStyle.Short)
.setValue('false')
.setPlaceholder('true or false')
)
);
await i.showModal(fieldModal);
break;
case 'edit_footer':
const footerModal = new ModalBuilder()
.setCustomId('footer_modal')
.setTitle('Edit Footer');
footerModal.addComponents(
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('footer_input')
.setLabel('Footer Text')
.setStyle(TextInputStyle.Short)
.setValue(messageData.footer || '')
.setMaxLength(2048)
)
);
await i.showModal(footerModal);
break;
case 'edit_image':
const imageModal = new ModalBuilder()
.setCustomId('image_modal')
.setTitle('Add Image');
imageModal.addComponents(
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('image_input')
.setLabel('Image URL')
.setStyle(TextInputStyle.Short)
.setValue(messageData.image || '')
.setPlaceholder('https://example.com/image.png')
)
);
await i.showModal(imageModal);
break;
case 'edit_thumbnail':
const thumbnailModal = new ModalBuilder()
.setCustomId('thumbnail_modal')
.setTitle('Add Thumbnail');
thumbnailModal.addComponents(
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('thumbnail_input')
.setLabel('Thumbnail URL')
.setStyle(TextInputStyle.Short)
.setValue(messageData.thumbnail || '')
.setPlaceholder('https://example.com/thumbnail.png')
)
);
await i.showModal(thumbnailModal);
break;
case 'toggle_timestamp':
messageData.timestamp = !messageData.timestamp;
await updatePreview(i);
break;
case 'send_message':
try {
if (messageData.type === 'embed') {
await channel.send({ embeds: [createEmbed()] });
} else {
await channel.send(messageData.description);
}
await i.update({
content: 'Message sent successfully!',
components: [],
embeds: []
});
collector.stop();
} catch (error) {
await i.reply({
content: 'Failed to send message. Please check channel permissions.',
ephemeral: true
});
}
break;
}
});
// Handle modal submissions
interaction.client.on('interactionCreate', async (modal) => {
if (!modal.isModalSubmit()) return;
if (modal.user.id !== interaction.user.id) return;
switch (modal.customId) {
case 'title_modal':
messageData.title = modal.fields.getTextInputValue('title_input');
break;
case 'desc_modal':
messageData.description = modal.fields.getTextInputValue('desc_input');
break;
case 'color_modal':
messageData.color = parseColor(modal.fields.getTextInputValue('color_input'));
break;
case 'field_modal':
const inlineValue = modal.fields.getTextInputValue('field_inline').toLowerCase();
messageData.fields.push({
name: modal.fields.getTextInputValue('field_name'),
value: modal.fields.getTextInputValue('field_value'),
inline: inlineValue === 'true'
});
break;
case 'footer_modal':
messageData.footer = modal.fields.getTextInputValue('footer_input');
break;
case 'image_modal':
messageData.image = modal.fields.getTextInputValue('image_input');
break;
case 'thumbnail_modal':
messageData.thumbnail = modal.fields.getTextInputValue('thumbnail_input');
break;
default:
return;
} }
if (EmbedAuthor) { await updatePreview(modal);
EmbedMsg.setAuthor({ name: EmbedAuthor, iconURL: EmbedAuthorIcon, url: EmbedAuthorURL }); });
}
if (EmbedTimestamp) { collector.on('end', () => {
EmbedMsg.setTimestamp(); interaction.editReply({
} content: 'Message editor timed out.',
components: [],
interaction.reply({ embeds: []
embeds: [EmbedMsg], }).catch(() => {});
}); });
} else { }
return interaction.reply({ embeds: [NoPerms] }); });
}
},
});
+134
View File
@@ -0,0 +1,134 @@
const fs = require("fs");
const {
EmbedBuilder,
ApplicationCommandOptionType,
ApplicationCommandType,
ChannelType,
} = require("discord.js");
const yaml = require("js-yaml");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const Command = require("../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.ForceAddUser.Command,
description: cmdconfig.ForceAddUser.Description,
type: ApplicationCommandType.ChatInput,
options: [
{
name: "user",
description: "The user to forcefully add",
type: ApplicationCommandOptionType.User,
required: true,
},
],
permissions: cmdconfig.ForceAddUser.Permission,
async run(interaction) {
const userToAdd = interaction.options.getUser("user");
const ticketChannel = interaction.channel;
const ticketDataPath = "./Data/TicketData.json";
if (
(supportbot.Ticket.TicketType === "threads" && !ticketChannel.isThread()) ||
(supportbot.Ticket.TicketType === "channels" && ticketChannel.type !== ChannelType.GuildText)
) {
const onlyInTicket = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.NotInTicket_Title)
.setDescription(msgconfig.ForceAddUser.NotInTicket_Description)
.setColor(supportbot.Embed.Colours.Error);
return interaction.reply({
embeds: [onlyInTicket],
ephemeral: true,
});
}
try {
if (supportbot.Ticket.TicketType === "threads") {
try {
// Explicitly ensure the bot is in the thread
const botMember = await ticketChannel.members.fetch(interaction.client.user.id).catch(() => null);
if (!botMember) {
await ticketChannel.join();
}
// Delay to ensure API has processed the bot's membership
await new Promise(res => setTimeout(res, 1000));
// Attempt to add the user to the thread
await ticketChannel.members.add(userToAdd.id);
console.log("User successfully added to the thread.");
} catch (err) {
console.error("Error adding user to the thread:", err.message);
const errorEmbed = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.Error_Title)
.setDescription(`An error occurred: ${err.message}`)
.setColor(supportbot.Embed.Colours.Error);
return interaction.reply({
embeds: [errorEmbed],
ephemeral: true,
});
}
} else if (supportbot.Ticket.TicketType === "channels") {
await ticketChannel.permissionOverwrites.create(userToAdd.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
const ticketData = JSON.parse(fs.readFileSync(ticketDataPath, "utf8"));
const ticketIndex = ticketData.tickets.findIndex((t) => t.id === ticketChannel.id);
if (ticketIndex !== -1) {
ticketData.tickets[ticketIndex].subUsers.push(userToAdd.id);
fs.writeFileSync(ticketDataPath, JSON.stringify(ticketData, null, 4));
}
const addedEmbed = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.Added_Title)
.setDescription(
msgconfig.ForceAddUser.Added_Description.replace("%username%", userToAdd.username)
)
.setColor(supportbot.Embed.Colours.Success);
await interaction.reply({
embeds: [addedEmbed],
ephemeral: true,
});
const addedToTicketEmbed = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.AddedToTicket_Title)
.setDescription(
msgconfig.ForceAddUser.AddedToTicket_Description.replace(
"%channel_link%",
`[${ticketChannel.name}](https://discord.com/channels/${ticketChannel.guild.id}/${ticketChannel.id})`
)
)
.setColor(supportbot.Embed.Colours.General);
await userToAdd.send({
embeds: [addedToTicketEmbed],
});
} catch (err) {
console.error("Error adding user to the ticket:", err);
const errorEmbed = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.Error_Title)
.setDescription(msgconfig.ForceAddUser.Error_Adding)
.setColor(supportbot.Embed.Colours.Error);
await interaction.reply({
embeds: [errorEmbed],
ephemeral: true,
});
}
},
});
+10 -21
View File
@@ -1,17 +1,9 @@
// SupportBot | Emerald Services
// Help Command
const fs = require("fs"); const fs = require("fs");
const Discord = require("discord.js"); const Discord = require("discord.js");
const yaml = require("js-yaml"); const yaml = require("js-yaml");
const supportbot = yaml.load( const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
fs.readFileSync("./Configs/supportbot.yml", "utf8") const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
);
const cmdconfig = yaml.load(
fs.readFileSync("./Configs/commands.yml", "utf8")
);
const Command = require("../Structures/Command.js"); const Command = require("../Structures/Command.js");
@@ -32,11 +24,17 @@ module.exports = new Command({
); );
} }
// Fetch all commands from the client's commands collection // Separate regular commands and addon commands
let botCommands = ""; let botCommands = "";
let addonCommands = "";
interaction.client.commands.forEach((command) => { interaction.client.commands.forEach((command) => {
if (command.permissions) { if (command.permissions) {
botCommands += `\`${command.name}\` `; if (command.addon) {
addonCommands += `\`${command.name}\` `;
} else {
botCommands += `\`${command.name}\` `;
}
} }
}); });
@@ -51,15 +49,6 @@ module.exports = new Command({
inline: false, inline: false,
}); });
// Conditionally add Addons section
if (supportbot.General.Addons.Enabled) {
HelpEmbed1.addFields({
name: "🛠️ Addons\n",
value: "No addons available.",
inline: false,
});
}
HelpEmbed1.setFooter({ HelpEmbed1.setFooter({
text: supportbot.Embed.Footer, text: supportbot.Embed.Footer,
iconURL: interaction.user.displayAvatarURL(), iconURL: interaction.user.displayAvatarURL(),
+261
View File
@@ -0,0 +1,261 @@
const fs = require("fs");
const { ApplicationCommandOptionType, ApplicationCommandType, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType } = require("discord.js");
const yaml = require("js-yaml");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const Command = require("../Structures/Command.js");
// Function to chunk array into smaller arrays
function chunkArray(arr, size) {
const chunks = [];
for (let i = 0; i < arr.length; i += size) {
chunks.push(arr.slice(i, i + size));
}
return chunks;
}
module.exports = new Command({
name: cmdconfig.Mod.Command,
description: cmdconfig.Mod.Command,
type: ApplicationCommandType.ChatInput,
options: [
{
name: cmdconfig.Mod.TicketBlacklist.Command,
description: cmdconfig.Mod.TicketBlacklist.Description,
type: ApplicationCommandOptionType.SubcommandGroup,
options: [
{
name: cmdconfig.Mod.TicketBlacklist.Add.Command,
description: msgconfig.Mod.TicketBlacklist.Add.Description || "Add a user to the ticket blacklist",
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'user',
description: msgconfig.Mod.TicketBlacklist.Add.UserDescription || "User to blacklist",
type: ApplicationCommandOptionType.User,
required: true
},
{
name: 'reason',
description: msgconfig.Mod.TicketBlacklist.Add.ReasonDescription || "Reason for blacklisting",
type: ApplicationCommandOptionType.String,
required: false
}
]
},
{
name: cmdconfig.Mod.TicketBlacklist.Remove.Command,
description: msgconfig.Mod.TicketBlacklist.Remove.Description || "Remove a user from the ticket blacklist",
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'user',
description: msgconfig.Mod.TicketBlacklist.Remove.UserDescription || "User to remove from blacklist",
type: ApplicationCommandOptionType.User,
required: true
},
{
name: 'reason',
description: msgconfig.Mod.TicketBlacklist.Remove.ReasonDescription || "Reason for removing from blacklist",
type: ApplicationCommandOptionType.String,
required: false
}
]
},
{
name: "view",
description: msgconfig.Mod.TicketBlacklist.View.Description || "View all blacklisted users",
type: ApplicationCommandOptionType.Subcommand,
}
]
}
],
permissions: cmdconfig.Mod.Permission,
async run(interaction) {
const { getRole } = interaction.client;
let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
let Moderator = await getRole(supportbot.Roles.StaffMember.Moderator, interaction.guild);
if (!SupportStaff || !Admin || !Moderator) {
const missingRolesEmbed = new EmbedBuilder()
.setDescription(msgconfig.Error.InvalidChannel || "Required roles are missing!")
.setColor(supportbot.Embed.Colours.Warn);
return interaction.reply({ embeds: [missingRolesEmbed] });
}
const NoPerms = new EmbedBuilder()
.setDescription(msgconfig.Error.IncorrectPerms || "You do not have the correct permissions!")
.setColor(supportbot.Embed.Colours.Warn);
if (!interaction.member.roles.cache.has(Admin.id) && !interaction.member.roles.cache.has(Moderator.id) && (!supportbot.Mod.AllowSupportStaff || !interaction.member.roles.cache.has(SupportStaff.id))) {
return interaction.reply({ embeds: [NoPerms] });
}
const subcommand = interaction.options.getSubcommand(false);
const user = interaction.options.getUser('user');
const reason = interaction.options.getString('reason') || "No reason provided";
try {
let blacklistedUsers;
try {
blacklistedUsers = JSON.parse(fs.readFileSync("./Data/BlacklistedUsers.json", "utf8")).blacklistedUsers;
if (!Array.isArray(blacklistedUsers)) {
blacklistedUsers = [];
}
} catch (error) {
blacklistedUsers = [];
}
if (subcommand === cmdconfig.Mod.TicketBlacklist.Add.Command) {
if (blacklistedUsers.includes(user.id)) {
const alreadyBlacklistedEmbed = new EmbedBuilder()
.setDescription(msgconfig.Mod.TicketBlacklist.Add.AlreadyBlacklisted.replace("{userTag}", user.tag) || ":x: User is already blacklisted.")
.setColor(supportbot.Embed.Colours.Warn);
return interaction.reply({ embeds: [alreadyBlacklistedEmbed], ephemeral: true });
}
blacklistedUsers.push(user.id);
fs.writeFileSync("./Data/BlacklistedUsers.json", JSON.stringify({ "blacklistedUsers": blacklistedUsers }, null, 4));
const successEmbed = new EmbedBuilder()
.setDescription(msgconfig.Mod.TicketBlacklist.Add.Success.replace("{userTag}", user.tag) || ":white_check_mark: User has been blacklisted.")
.setColor(supportbot.Embed.Colours.Success);
const blacklistChannel = interaction.guild.channels.cache.get(supportbot.Ticket.Log.TicketBlacklistLog);
if (blacklistChannel) {
const blacklistLogEmbed = new EmbedBuilder()
.setTitle(msgconfig.TicketBlacklistLog.Title || "Ticket Blacklist Log")
.setColor(msgconfig.TicketBlacklistLog.Colour || supportbot.Embed.Colours.Success)
.setFooter({ text: supportbot.Embed.Footer, iconURL: interaction.user.displayAvatarURL() })
.setDescription(`> **User:** ${user.tag} (\`${user.id}\`)\n> **Actioned by:** <@${interaction.user.id}>`)
.addFields({ name: "Action", value: "\`\`\`Removed from blacklist\`\`\`", inline: false })
.addFields({ name: "Reason", value: `\`\`\`${reason}\`\`\``, inline: false });
blacklistChannel.send({ embeds: [blacklistLogEmbed] });
}
return interaction.reply({
embeds: [successEmbed],
ephemeral: true
});
} else if (subcommand === cmdconfig.Mod.TicketBlacklist.Remove.Command) {
if (!blacklistedUsers.includes(user.id)) {
const notBlacklistedEmbed = new EmbedBuilder()
.setDescription(msgconfig.Mod.TicketBlacklist.Remove.NotBlacklisted.replace("{userTag}", user.tag) || ":x: User is not blacklisted.")
.setColor(supportbot.Embed.Colours.Warn);
return interaction.reply({ embeds: [notBlacklistedEmbed], ephemeral: true });
}
blacklistedUsers = blacklistedUsers.filter(id => id !== user.id);
fs.writeFileSync("./Data/BlacklistedUsers.json", JSON.stringify({ "blacklistedUsers": blacklistedUsers }, null, 4));
const removedEmbed = new EmbedBuilder()
.setDescription(msgconfig.Mod.TicketBlacklist.Remove.Success.replace("{userTag}", user.tag) || ":white_check_mark: User has been removed from the blacklist.")
.setColor(supportbot.Embed.Colours.Success);
const blacklistChannel = interaction.guild.channels.cache.get(supportbot.Ticket.Log.TicketBlacklistLog);
if (blacklistChannel) {
const blacklistLogEmbed = new EmbedBuilder()
.setTitle(msgconfig.TicketBlacklistLog.Title || "Ticket Blacklist Log")
.setColor(msgconfig.TicketBlacklistLog.Colour || supportbot.Embed.Colours.Success)
.setFooter({ text: supportbot.Embed.Footer, iconURL: interaction.user.displayAvatarURL() })
.setDescription(`> **User:** ${user.tag} (\`${user.id}\`)\n> **Actioned by:** <@${interaction.user.id}>`)
.addFields({ name: "Action", value: "\`\`\`Removed from blacklist\`\`\`", inline: false })
.addFields({ name: "Reason", value: `\`\`\`${reason}\`\`\``, inline: false });
blacklistChannel.send({ embeds: [blacklistLogEmbed] });
}
return interaction.reply({ embeds: [removedEmbed], ephemeral: true });
} else if (subcommand === "view") {
if (blacklistedUsers.length === 0) {
const noBlacklistedUsersEmbed = new EmbedBuilder()
.setDescription(msgconfig.Mod.TicketBlacklist.View.NoBlacklistedUsers || "No users are blacklisted.")
.setColor(supportbot.Embed.Colours.Success);
return interaction.reply({ embeds: [noBlacklistedUsersEmbed], ephemeral: true });
}
const chunkedUsers = chunkArray(blacklistedUsers, 5);
let currentPage = 0;
const createEmbed = (page) => {
return new EmbedBuilder()
.setTitle(msgconfig.Mod.TicketBlacklist.View.EmbedTitle || "Blacklisted Users")
.setDescription(chunkedUsers[page].map(id => `<@${id}>`).join("\n"))
.setColor(supportbot.Embed.Colours.Success)
.setFooter({ text: `Page ${page + 1} of ${chunkedUsers.length}` });
};
const buttons = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('previous')
.setLabel('Previous')
.setStyle(ButtonStyle.Secondary)
.setDisabled(currentPage === 0),
new ButtonBuilder()
.setCustomId('next')
.setLabel('Next')
.setStyle(ButtonStyle.Secondary)
.setDisabled(currentPage === chunkedUsers.length - 1)
);
const response = await interaction.reply({
embeds: [createEmbed(currentPage)],
components: chunkedUsers.length > 1 ? [buttons] : [],
ephemeral: true
});
if (chunkedUsers.length <= 1) return;
const collector = response.createMessageComponentCollector({
componentType: ComponentType.Button,
time: 60000
});
collector.on('collect', async (i) => {
if (i.user.id !== interaction.user.id) {
return i.reply({
content: 'You cannot use these buttons.',
ephemeral: true
});
}
if (i.customId === 'previous' && currentPage > 0) {
currentPage--;
} else if (i.customId === 'next' && currentPage < chunkedUsers.length - 1) {
currentPage++;
}
buttons.components[0].setDisabled(currentPage === 0);
buttons.components[1].setDisabled(currentPage === chunkedUsers.length - 1);
await i.update({
embeds: [createEmbed(currentPage)],
components: [buttons]
});
});
collector.on('end', () => {
buttons.components.forEach(button => button.setDisabled(true));
interaction.editReply({
components: [buttons]
}).catch(() => {});
});
}
} catch (error) {
console.error("Error in mod command:", error);
const errorEmbed = new EmbedBuilder()
.setDescription(msgconfig.Error.ActionFailed || "An error occurred while processing your request.")
.setColor(supportbot.Embed.Colours.Warn);
interaction.reply({ embeds: [errorEmbed], ephemeral: true });
}
}
});
+320
View File
@@ -0,0 +1,320 @@
const { EmbedBuilder, ModalBuilder, TextInputBuilder, TextInputStyle, ApplicationCommandType, ActionRowBuilder, ButtonBuilder, ButtonStyle, ApplicationCommandOptionType } = require("discord.js");
const fs = require("fs");
const yaml = require("js-yaml");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const Command = require("../Structures/Command.js");
const clockedInUsers = new Set();
function updateClockedInStatus(profileEmbed, clockedIn) {
profileEmbed.data.fields = profileEmbed.data.fields.map(field => {
if (field.name === 'Clocked In Status') {
field.value = clockedIn ? '✅ Clocked In' : '❌ Clocked Out';
}
return field;
});
return profileEmbed;
}
module.exports = new Command({
name: cmdconfig.Profile.Command,
description: cmdconfig.Profile.Description,
type: ApplicationCommandType.ChatInput,
permissions: cmdconfig.Profile.Permission,
options: [
{
name: 'user',
description: 'The user to view the profile of',
type: ApplicationCommandOptionType.User,
required: false
}
],
async run(interaction) {
await interaction.deferReply({ ephemeral: true });
const userOption = interaction.options.getUser('user');
const viewingUser = userOption || interaction.user;
const viewingUserId = viewingUser.id;
const interactionUserId = interaction.user.id;
const user = interaction.guild.members.cache.get(viewingUserId);
const { getRole, getChannel } = interaction.client;
const Staff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
const Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!Staff || !Admin) {
return interaction.editReply({
content: "Some roles seem to be missing!\nPlease check for errors when starting the bot.",
ephemeral: true,
});
}
const isStaff = user.roles.cache.has(Staff.id) || user.roles.cache.has(Admin.id);
const isOwnProfile = interactionUserId === viewingUserId;
const profilePath = `./Data/Profiles/${viewingUserId}.json`;
let profileData;
if (fs.existsSync(profilePath)) {
profileData = JSON.parse(fs.readFileSync(profilePath, "utf8"));
} else {
profileData = {
bio: "",
timezone: "",
clockedIn: false
};
fs.writeFileSync(profilePath, JSON.stringify(profileData, null, 2));
}
const { bio, timezone, clockedIn } = profileData;
let profileEmbed = new EmbedBuilder()
.setTitle(`${viewingUser.username}'s Profile`)
.setColor(supportbot.Embed.Colours.General)
.setThumbnail(viewingUser.displayAvatarURL())
.addFields(
{ name: 'Bio', value: bio || 'No bio set.', inline: false },
{ name: 'Timezone', value: timezone || 'No timezone set.', inline: true }
);
if (isStaff) {
profileEmbed.addFields({ name: 'Clocked In Status', value: clockedIn ? '✅ Clocked In' : '❌ Clocked Out', inline: true });
}
const buttonRow = new ActionRowBuilder();
if (isOwnProfile) {
buttonRow.addComponents(
new ButtonBuilder()
.setCustomId('editProfile')
.setLabel('Edit Profile')
.setStyle(ButtonStyle.Secondary)
);
if (isStaff) {
buttonRow.addComponents(
new ButtonBuilder()
.setCustomId('clockInOut')
.setLabel(clockedIn ? 'Clock Out' : 'Clock In')
.setStyle(ButtonStyle.Secondary)
);
}
}
if (isStaff && supportbot.Ticket.ClaimTickets.Enabled) {
buttonRow.addComponents(
new ButtonBuilder()
.setCustomId('viewTicketStats')
.setLabel('View Ticket Stats')
.setStyle(ButtonStyle.Secondary)
);
}
await interaction.editReply({ embeds: [profileEmbed], components: buttonRow.components.length > 0 ? [buttonRow] : [] });
const filter = i => (
['editProfile', 'clockInOut', 'viewTicketStats', 'showOpenTickets', 'backToStats', 'backToProfile'].includes(i.customId)
&& i.user.id === interactionUserId
);
const collector = interaction.channel.createMessageComponentCollector({ filter, time: 60000 });
collector.on('collect', async i => {
if (i.customId === 'editProfile') {
const modal = new ModalBuilder()
.setCustomId('editProfileModal')
.setTitle('Edit Profile')
.addComponents(
new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setCustomId('bio')
.setLabel('Bio')
.setStyle(TextInputStyle.Paragraph)
.setPlaceholder('Enter your bio')
.setValue(String(bio))
.setRequired(false)
),
new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setCustomId('timezone')
.setLabel('Timezone')
.setStyle(TextInputStyle.Short)
.setPlaceholder('Enter your timezone')
.setValue(String(timezone))
.setRequired(false)
)
);
await i.showModal(modal);
}
if (i.customId === 'clockInOut') {
profileData.clockedIn = !profileData.clockedIn;
fs.writeFileSync(profilePath, JSON.stringify(profileData, null, 2));
profileEmbed = updateClockedInStatus(profileEmbed, profileData.clockedIn);
if (profileData.clockedIn) {
clockedInUsers.add(viewingUserId);
} else {
clockedInUsers.delete(viewingUserId);
}
const updatedButtonRow = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('editProfile')
.setLabel('Edit Profile')
.setStyle(ButtonStyle.Secondary)
);
if (isStaff) {
updatedButtonRow.addComponents(
new ButtonBuilder()
.setCustomId('clockInOut')
.setLabel(profileData.clockedIn ? 'Clock Out' : 'Clock In')
.setStyle(ButtonStyle.Secondary)
);
if (supportbot.Ticket.ClaimTickets.Enabled) {
updatedButtonRow.addComponents(
new ButtonBuilder()
.setCustomId('viewTicketStats')
.setLabel('View Ticket Stats')
.setStyle(ButtonStyle.Secondary)
);
}
}
await i.update({ embeds: [profileEmbed], components: [updatedButtonRow] });
let clockedinout = new EmbedBuilder()
.setTitle(profileData.clockedIn ? 'Clocked In!' : 'Clocked Out!')
.setDescription(profileData.clockedIn ? 'You have successfully clocked in.' : 'You have successfully clocked out.')
.setColor(supportbot.Embed.Colours.General)
await interaction.followUp({ embeds: [clockedinout], ephemeral: true });
}
if (i.customId === 'viewTicketStats') {
const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketsClaimed = TicketData.tickets.filter(t => t.claimedBy === viewingUserId);
const ticketsOpen = ticketsClaimed.filter(t => t.open);
const totalTickets = ticketsClaimed.length;
let totalResponseTime = 0;
let responseCount = 0;
ticketsClaimed.forEach(ticket => {
if (ticket.claimedAt && ticket.createdAt) {
const responseTime = new Date(ticket.claimedAt) - new Date(ticket.createdAt);
totalResponseTime += responseTime;
responseCount++;
}
});
const averageResponseTime = responseCount ? totalResponseTime / responseCount : 0;
const averageResponseTimeMinutes = Math.round(averageResponseTime / 60000);
const statsEmbed = new EmbedBuilder()
.setTitle(msgconfig.TicketStats.Title)
.setDescription(`> Ticket stats for <@${viewingUserId}>`)
.setColor(supportbot.Embed.Colours.General)
.addFields(
{ name: msgconfig.TicketStats.OpenTickets, value: `${ticketsOpen.length}`, inline: true },
{ name: msgconfig.TicketStats.TotalTickets, value: `${totalTickets}`, inline: true },
{ name: msgconfig.TicketStats.ResponseTime, value: `${averageResponseTimeMinutes} minutes`, inline: false }
);
const statsButtonRow = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('showOpenTickets')
.setLabel('Show Open Tickets')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('backToProfile')
.setLabel('Back to Profile')
.setStyle(ButtonStyle.Secondary)
);
await i.update({ embeds: [statsEmbed], components: [statsButtonRow] });
}
if (i.customId === 'showOpenTickets') {
const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketsOpen = TicketData.tickets.filter(t => t.claimedBy === viewingUserId && t.open);
const openTicketsEmbed = new EmbedBuilder()
.setTitle('Active Tickets')
.setDescription(ticketsOpen.map(t => `<#${t.id}>`).join('\n') || 'No active tickets.')
.setColor(supportbot.Embed.Colours.General);
const openTicketsButtonRow = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('backToStats')
.setLabel('Back to Stats')
.setStyle(ButtonStyle.Secondary)
);
await i.update({ embeds: [openTicketsEmbed], components: [openTicketsButtonRow] });
}
if (i.customId === 'backToStats') {
const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketsClaimed = TicketData.tickets.filter(t => t.claimedBy === viewingUserId);
const ticketsOpen = ticketsClaimed.filter(t => t.open);
const totalTickets = ticketsClaimed.length;
let totalResponseTime = 0;
let responseCount = 0;
ticketsClaimed.forEach(ticket => {
if (ticket.claimedAt && ticket.createdAt) {
const responseTime = new Date(ticket.claimedAt) - new Date(ticket.createdAt);
totalResponseTime += responseTime;
responseCount++;
}
});
const averageResponseTime = responseCount ? totalResponseTime / responseCount : 0;
const averageResponseTimeMinutes = Math.round(averageResponseTime / 60000);
const statsEmbed = new EmbedBuilder()
.setTitle(msgconfig.TicketStats.Title)
.setDescription(`> Ticket stats for <@${viewingUserId}>`)
.setColor(supportbot.Embed.Colours.General)
.addFields(
{ name: msgconfig.TicketStats.OpenTickets, value: `${ticketsOpen.length}`, inline: true },
{ name: msgconfig.TicketStats.TotalTickets, value: `${totalTickets}`, inline: true },
{ name: msgconfig.TicketStats.ResponseTime, value: `${averageResponseTimeMinutes} minutes`, inline: false }
);
const statsButtonRow = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('showOpenTickets')
.setLabel('Show Open Tickets')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('backToProfile')
.setLabel('Back to Profile')
.setStyle(ButtonStyle.Secondary)
);
await i.update({ embeds: [statsEmbed], components: [statsButtonRow] });
}
if (i.customId === 'backToProfile') {
await i.update({ embeds: [profileEmbed], components: buttonRow.components.length > 0 ? [buttonRow] : [] });
}
});
}
});
module.exports.clockedInUsers = clockedInUsers;
+134
View File
@@ -0,0 +1,134 @@
const fs = require("fs");
const {
EmbedBuilder,
ApplicationCommandOptionType,
ApplicationCommandType,
ChannelType,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
} = require("discord.js");
const yaml = require("js-yaml");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const Command = require("../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.RemoveUser.Command,
description: cmdconfig.RemoveUser.Description,
type: ApplicationCommandType.ChatInput,
options: [
{
name: "user",
description: "The user to remove",
type: ApplicationCommandOptionType.User,
required: true,
},
],
permissions: cmdconfig.RemoveUser.Permission,
async run(interaction) {
const { getRole } = interaction.client;
let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!SupportStaff || !Admin) {
return interaction.reply(
"Some roles seem to be missing!\nPlease check for errors when starting the bot."
);
}
const NoPerms = new EmbedBuilder()
.setTitle("Invalid Permissions!")
.setDescription(
`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``
)
.setColor(supportbot.Embed.Colours.Warn);
if (
!interaction.member.roles.cache.has(SupportStaff.id) &&
!interaction.member.roles.cache.has(Admin.id)
) {
return interaction.reply({ embeds: [NoPerms] });
}
const userToRemove = interaction.options.getUser("user");
const ticketChannel = interaction.channel;
const ticketDataPath = "./Data/TicketData.json";
// Check if the channel is a ticket thread or text channel based on TicketType
if (
(supportbot.Ticket.TicketType === "threads" && !ticketChannel.isThread()) ||
(supportbot.Ticket.TicketType === "channels" && ticketChannel.type !== ChannelType.GuildText)
) {
const onlyInTicket = new EmbedBuilder()
.setTitle(msgconfig.RemoveUser.NotInTicket_Title)
.setDescription(msgconfig.RemoveUser.NotInTicket_Description)
.setColor(supportbot.Embed.Colours.Error);
return interaction.reply({
embeds: [onlyInTicket],
ephemeral: true,
});
}
try {
// Remove user from thread or channel based on TicketType
if (supportbot.Ticket.TicketType === "threads") {
await ticketChannel.members.remove(userToRemove.id); // For threads
} else if (supportbot.Ticket.TicketType === "channels") {
await ticketChannel.permissionOverwrites.delete(userToRemove.id); // For channels
}
// Update the ticket data
const ticketData = JSON.parse(fs.readFileSync(ticketDataPath, "utf8"));
const ticketIndex = ticketData.tickets.findIndex((t) => t.id === ticketChannel.id);
if (ticketIndex !== -1) {
const subUsers = ticketData.tickets[ticketIndex].subUsers;
const userIndex = subUsers.indexOf(userToRemove.id);
if (userIndex !== -1) {
subUsers.splice(userIndex, 1);
fs.writeFileSync(ticketDataPath, JSON.stringify(ticketData, null, 4));
}
}
const removedEmbed = new EmbedBuilder()
.setTitle(msgconfig.RemoveUser.Removed_Title)
.setDescription(msgconfig.RemoveUser.Removed_Message)
.setColor(supportbot.Embed.Colours.Success);
await interaction.reply({
embeds: [removedEmbed],
ephemeral: true,
});
const removedFromTicketEmbed = new EmbedBuilder()
.setTitle(msgconfig.RemoveUser.RemovedFromTicket_Title)
.setDescription(msgconfig.RemoveUser.RemovedFromTicket_Description.replace(
"%channel_link%",
`[${ticketChannel.name}](https://discord.com/channels/${ticketChannel.guild.id}/${ticketChannel.id})`
))
.setColor(supportbot.Embed.Colours.General);
await userToRemove.send({
embeds: [removedFromTicketEmbed],
});
} catch (err) {
console.error("Error removing user from the ticket:", err);
const errorEmbed = new EmbedBuilder()
.setTitle(msgconfig.RemoveUser.Error_Title)
.setDescription(msgconfig.RemoveUser.Error_Removing)
.setColor(supportbot.Embed.Colours.Error);
await interaction.reply({
embeds: [errorEmbed],
ephemeral: true,
});
}
},
});
+235
View File
@@ -0,0 +1,235 @@
const { Command } = require('../Structures/Addon.js');
const Discord = require("discord.js");
const fs = require("fs");
const yaml = require("js-yaml");
const path = require("path");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const settingsFilePath = path.resolve(__dirname, '../Data/settings.json');
function loadSettings() {
try {
if (fs.existsSync(settingsFilePath)) {
const data = fs.readFileSync(settingsFilePath, 'utf8');
return JSON.parse(data);
} else {
return {};
}
} catch (error) {
console.error("Error reading settings file:", error);
return {};
}
}
function saveSettings(settings) {
try {
fs.writeFileSync(settingsFilePath, JSON.stringify(settings, null, 2), 'utf8');
} catch (error) {
console.error("Error writing settings file:", error);
}
}
module.exports = new Command({
name: cmdconfig.Settings.Command,
description: cmdconfig.Settings.Description,
permissions: cmdconfig.Settings.Permission,
options: [
{
name: 'setstatus',
type: Discord.ApplicationCommandOptionType.Subcommand,
description: 'Set the bot status',
options: [
{
name: 'status',
type: Discord.ApplicationCommandOptionType.String,
description: 'Choose the bot status',
required: true,
choices: [
{ name: 'Online 🟢', value: 'online' },
{ name: 'Idle 🌙', value: 'idle' },
{ name: 'Do Not Disturb ⛔', value: 'dnd' },
{ name: 'Invisible 👻', value: 'invisible' }
],
},
],
},
{
name: 'setactivity',
type: Discord.ApplicationCommandOptionType.Subcommand,
description: 'Set the bot activity',
options: [
{
name: 'type',
type: Discord.ApplicationCommandOptionType.String,
description: 'Choose the activity type',
required: true,
choices: [
{ name: 'Playing 🎮', value: 'PLAYING' },
{ name: 'Watching 👀', value: 'WATCHING' },
{ name: 'Listening 🎧', value: 'LISTENING' },
{ name: 'Competing 🏆', value: 'COMPETING' }
],
},
{
name: 'message',
type: Discord.ApplicationCommandOptionType.String,
description: 'The activity message',
required: true,
},
],
},
{
name: 'streammode',
type: Discord.ApplicationCommandOptionType.Subcommand,
description: 'Set the bot streaming status',
options: [
{
name: 'toggle',
type: Discord.ApplicationCommandOptionType.String,
description: 'Turn streaming mode on or off',
required: true,
choices: [
{ name: 'On', value: 'on' },
{ name: 'Off', value: 'off' }
],
},
{
name: 'twitchurl',
type: Discord.ApplicationCommandOptionType.String,
description: 'Twitch stream URL (Required if turning on)',
required: false,
},
],
},
{
name: 'setnickname',
type: Discord.ApplicationCommandOptionType.Subcommand,
description: 'Set the bot nickname',
options: [
{
name: 'nickname',
type: Discord.ApplicationCommandOptionType.String,
description: 'The new nickname for the bot',
required: true,
},
],
},
],
async run(interaction) {
const subcommand = interaction.options.getSubcommand();
const settings = loadSettings();
if (subcommand === 'setstatus') {
const status = interaction.options.getString('status');
try {
interaction.client.user.setPresence({
status: status,
activities: interaction.client.user.presence.activities || [],
});
settings.status = status;
saveSettings(settings);
const embed = new Discord.EmbedBuilder()
.setDescription(`Status set to **${status.charAt(0).toUpperCase() + status.slice(1)}**`)
.setColor(supportbot.Embed.Colours.General);
return interaction.reply({ embeds: [embed], ephemeral: true });
} catch (error) {
return interaction.reply({ content: `Failed to set status: ${error.message}`, ephemeral: true });
}
}
if (subcommand === 'setactivity') {
const activityType = interaction.options.getString('type');
const activityMessage = interaction.options.getString('message');
try {
interaction.client.user.setPresence({
activities: [{ name: activityMessage, type: Discord.ActivityType[activityType] }],
status: interaction.client.user.presence.status || 'online',
});
settings.activity = { type: activityType, message: activityMessage };
saveSettings(settings);
const embed = new Discord.EmbedBuilder()
.setDescription(`Activity set to **${activityType.toLowerCase()} ${activityMessage}**`)
.setColor(supportbot.Embed.Colours.General);
return interaction.reply({ embeds: [embed], ephemeral: true });
} catch (error) {
return interaction.reply({ content: `Failed to set activity: ${error.message}`, ephemeral: true });
}
} else if (subcommand === 'streammode') {
const toggle = interaction.options.getString('toggle');
const twitchUrl = interaction.options.getString('twitchurl');
if (toggle === 'on') {
if (!twitchUrl) {
return interaction.reply({ content: 'Please provide a Twitch URL to stream.', ephemeral: true });
}
try {
interaction.client.user.setPresence({
activities: [{ name: 'Streaming', type: Discord.ActivityType.Streaming, url: twitchUrl }],
status: 'online',
});
settings.streaming = { active: true, url: twitchUrl };
saveSettings(settings);
const embed = new Discord.EmbedBuilder()
.setDescription(`**Streaming mode enabled!**\nStreaming: [Twitch](${twitchUrl})`)
.setColor(supportbot.Embed.Colours.General);
return interaction.reply({ embeds: [embed], ephemeral: true });
} catch (error) {
return interaction.reply({ content: `Failed to enable streaming mode: ${error.message}`, ephemeral: true });
}
} else if (toggle === 'off') {
try {
interaction.client.user.setPresence({
activities: interaction.client.user.presence.activities.filter(a => a.type !== Discord.ActivityType.Streaming),
status: settings.status || 'online',
});
settings.streaming = { active: false };
saveSettings(settings);
const embed = new Discord.EmbedBuilder()
.setDescription(`**Streaming mode disabled!**`)
.setColor(supportbot.Embed.Colours.General);
return interaction.reply({ embeds: [embed], ephemeral: true });
} catch (error) {
return interaction.reply({ content: `Failed to disable streaming mode: ${error.message}`, ephemeral: true });
}
}
} else if (subcommand === 'setnickname') {
const nickname = interaction.options.getString('nickname');
try {
const guild = interaction.guild;
const botMember = guild.members.cache.get(interaction.client.user.id);
await botMember.setNickname(nickname);
settings.nickname = nickname;
saveSettings(settings);
const embed = new Discord.EmbedBuilder()
.setDescription(`Bot nickname set to **${nickname}**`)
.setColor(supportbot.Embed.Colours.General);
return interaction.reply({ embeds: [embed], ephemeral: true });
} catch (error) {
return interaction.reply({ content: `Failed to set nickname: ${error.message}`, ephemeral: true });
}
} else {
return interaction.reply({ content: 'Invalid command usage.', ephemeral: true });
}
},
});
+481 -239
View File
@@ -8,7 +8,8 @@ const {
ButtonBuilder, ButtonBuilder,
StringSelectMenuBuilder, StringSelectMenuBuilder,
StringSelectMenuOptionBuilder, StringSelectMenuOptionBuilder,
GuildMemberRoleManager, ButtonStyle,
MessageCollector,
} = require("discord.js"); } = require("discord.js");
const yaml = require("js-yaml"); const yaml = require("js-yaml");
@@ -20,6 +21,248 @@ const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const Command = require("../Structures/Command.js"); const Command = require("../Structures/Command.js");
const TicketNumberID = require("../Structures/TicketID.js"); const TicketNumberID = require("../Structures/TicketID.js");
async function isBlacklisted(userId) {
let blacklistData;
try {
blacklistData = JSON.parse(fs.readFileSync("./Data/BlacklistedUsers.json", "utf8"));
} catch (error) {
console.error("Error reading BlacklistedUsers.json:", error);
return false;
}
if (!blacklistData || !Array.isArray(blacklistData.blacklistedUsers)) {
console.error("blacklistedUsers is not defined or is not an array");
return false;
}
return blacklistData.blacklistedUsers.includes(userId);
}
async function getClockedInUsers(guild) {
const clockedInUsers = new Set();
const profilesDir = './Data/Profiles';
const profileFiles = fs.readdirSync(profilesDir);
for (const file of profileFiles) {
const profileData = JSON.parse(fs.readFileSync(`${profilesDir}/${file}`, "utf8"));
if (profileData.clockedIn) {
const userId = file.replace('.json', '');
const member = await guild.members.fetch(userId);
if (member) clockedInUsers.add(member.id);
}
}
return clockedInUsers;
}
async function askTicketQuestions(ticketChannel, interaction, questions) {
const answers = [];
let questionIndex = 0;
const sendNextQuestion = async () => {
if (questionIndex < questions.length) {
const questionsEmbed = new EmbedBuilder()
.setDescription(`**${questionIndex + 1}.** ${questions[questionIndex]}`)
.setColor(supportbot.Embed.Colours.General)
const questionMessage = await ticketChannel.send({
embeds: [questionsEmbed],
});
const collector = new MessageCollector(ticketChannel, {
filter: (m) => m.author.id === interaction.user.id,
max: 1,
time: 60000,
});
collector.on("collect", async (message) => {
answers.push({ question: questions[questionIndex], answer: message.content });
questionIndex++;
await message.delete();
await questionMessage.delete();
sendNextQuestion();
});
} else {
const summaryEmbed = new EmbedBuilder()
.setTitle(msgconfig.Ticket.TicketQuestions.Details.Title)
.setDescription(msgconfig.Ticket.TicketQuestions.Details.Description.replace('%user%', interaction.user.id))
.setColor(supportbot.Embed.Colours.General)
.addFields(
answers.map((a) => ({
name: `Q: ${a.question}`,
value: `A: ${a.answer}`,
inline: false,
}))
)
.setTimestamp();
await ticketChannel.send({
embeds: [summaryEmbed]
});
}
};
// Start asking the first question
sendNextQuestion();
}
async function assignTicketToUser(ticketChannel, Staff, Admin, interaction, attempts = 0) {
try {
const clockedInUsers = await getClockedInUsers(interaction.guild);
if (clockedInUsers.size === 0 || attempts >= 2) {
const claimChannel = await interaction.guild.channels.cache.get(supportbot.Ticket.ClaimTickets.Channel);
if (claimChannel) {
const claimEmbed = new EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.ClaimTitle)
.setDescription(msgconfig.Ticket.ClaimTickets.ClaimMessage)
.setColor(supportbot.Embed.Colours.General);
const claimButton = new ButtonBuilder()
.setCustomId(`claimticket-${ticketChannel.id}`)
.setLabel(supportbot.Ticket.ClaimTickets.ButtonTitle || 'Claim Ticket')
.setStyle(ButtonStyle.Primary);
const row = new ActionRowBuilder().addComponents(claimButton);
const claimMessage = await claimChannel.send({
content: `<@&${Staff.id}> <@&${Admin.id}>`,
embeds: [claimEmbed],
components: [row],
});
const filter = i => i.customId === `claimticket-${ticketChannel.id}` && i.member.roles.cache.has(Staff.id);
const collector = claimMessage.createMessageComponentCollector({ filter, time: 120000 });
collector.on('collect', async i => {
await ticketChannel.send({ content: `<@${i.user.id}> has claimed the ticket.` });
});
collector.on('end', async collected => {
if (collected.size === 0) {
await claimMessage.delete();
await claimChannel.send({
content: `Ticket is still unclaimed. \nCC: [<@&${Staff.id}> <@&${Admin.id}]`,
embeds: [new EmbedBuilder().setDescription(`Ticket is still unclaimed: <#${ticketChannel.id}>`).setColor(supportbot.Embed.Colours.Warn)],
});
}
});
return null;
} else {
console.error("Claim channel not found");
return null;
}
}
const clockedInArray = Array.from(clockedInUsers);
const assignedUserId = clockedInArray[Math.floor(Math.random() * clockedInArray.length)];
const assignedUser = await interaction.guild.members.fetch(assignedUserId);
const claimEmbed = new EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.ClaimTitle)
.setDescription(msgconfig.Ticket.ClaimTickets.ClaimMessage.replace('%user%', interaction.user.id))
.setColor(supportbot.Embed.Colours.General);
const claimButton = new ButtonBuilder()
.setCustomId(`claimticket-${ticketChannel.id}`)
.setLabel(supportbot.Ticket.ClaimTickets.ButtonTitle || 'Claim Ticket')
.setStyle(ButtonStyle.Primary);
const row = new ActionRowBuilder().addComponents(claimButton);
const claimChannel = await interaction.guild.channels.cache.get(supportbot.Ticket.ClaimTickets.Channel);
if (!claimChannel) {
console.error("Claim channel not found");
return null;
}
const claimMessage = await claimChannel.send({
content: `${assignedUser}`,
embeds: [claimEmbed],
components: [row],
});
const filter = i => i.customId === `claimticket-${ticketChannel.id}` && i.user.id === assignedUserId;
const collector = claimMessage.createMessageComponentCollector({ filter, time: 120000 });
collector.on('collect', async i => {
await ticketChannel.send({ content: `<@${assignedUserId}>` }).then(msg => setTimeout(() => msg.delete(), 5000));
});
collector.on('end', async collected => {
if (collected.size === 0) {
await claimMessage.delete();
if (attempts >= 1) {
await claimChannel.send({
content: `No one claimed the ticket. Tagging roles: <@&${Staff.id}> <@&${Admin.id}>`,
embeds: [new EmbedBuilder().setDescription(`Ticket is still unclaimed: <#${ticketChannel.id}>`).setColor(supportbot.Embed.Colours.Warn)],
});
const claimEmbed = new EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.ClaimTitle)
.setDescription(msgconfig.Ticket.ClaimTickets.ClaimMessage)
.setColor(supportbot.Embed.Colours.General);
const claimButton = new ButtonBuilder()
.setCustomId(`claimticket-${ticketChannel.id}`)
.setLabel(supportbot.Ticket.ClaimTickets.ButtonTitle)
.setEmoji(supportbot.Ticket.ClaimTickets.ButtonEmoji)
.setStyle(ButtonStyle.Primary);
const row = new ActionRowBuilder().addComponents(claimButton);
const claimMessage = await claimChannel.send({
embeds: [claimEmbed],
components: [row],
});
const filter = i => i.customId === `claimticket-${ticketChannel.id}` && i.member.roles.cache.has(Staff.id);
const collector = claimMessage.createMessageComponentCollector({ filter, time: 120000 });
collector.on('collect', async i => {
console.log(`Ticket ${ticketChannel.id} claimed by user ${i.user.id}`);
await ticketChannel.member.add(i.user.id);
});
collector.on('end', async collected => {
if (collected.size === 0) {
console.log(`No one claimed the ticket (${ticketChannel.id}), notifying all staff`);
await claimMessage.delete();
await claimChannel.send({
content: `No one claimed the ticket. Tagging roles: <@&${Staff.id}> <@&${Admin.id}>`,
});
}
});
} else {
const ReassigningEmbed = new EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.ReassigningTitle)
.setDescription(msgconfig.Ticket.ClaimTickets.ReassigningMessage.replace('%user%', assignedUserId))
.setColor(supportbot.Embed.Colours.General);
await claimChannel.send({ embeds: [ReassigningEmbed], components: [] });
await assignTicketToUser(ticketChannel, Staff, Admin, interaction, attempts + 1);
}
}
});
return assignedUserId;
} catch (error) {
console.error("Error in assignTicketToUser function:", error);
if (error instanceof CombinedError) {
console.error("CombinedError details:", error.errors);
}
return null;
}
}
module.exports = new Command({ module.exports = new Command({
name: cmdconfig.OpenTicket.Command, name: cmdconfig.OpenTicket.Command,
description: cmdconfig.OpenTicket.Description, description: cmdconfig.OpenTicket.Description,
@@ -34,275 +277,274 @@ module.exports = new Command({
permissions: cmdconfig.OpenTicket.Permission, permissions: cmdconfig.OpenTicket.Permission,
async run(interaction) { async run(interaction) {
let department = interaction.customId?.split("-")[1] || null;
let TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8")); try {
const { getRole, getChannel } = interaction.client;
let User = interaction.guild.members.cache.get(interaction.user.id);
if ( if (await isBlacklisted(interaction.user.id)) {
supportbot.Ticket.TicketsPerUser && return interaction.reply({
TicketData.tickets.filter((t) => t.user === interaction.user.id && t.open).length >= supportbot.Ticket.TicketsPerUser content: msgconfig.Ticket.Blacklisted,
) { ephemeral: true,
return interaction.reply({ });
embeds: [ }
{
title: "Too Many Tickets!", let TicketReason = null;
description: `You can't have more than ${supportbot.Ticket.TicketsPerUser} open tickets!`, if (supportbot.Ticket.TicketReason) {
color: supportbot.Embed.Colours.Warn, TicketReason = interaction.reason || null; // Retrieve the reason from the interaction object
}, }
],
ephemeral: true, let TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
}); const { getRole, getChannel } = interaction.client;
let User = interaction.guild.members.cache.get(interaction.user.id);
if (
supportbot.Ticket.TicketsPerUser &&
TicketData.tickets.filter((t) => t.user === interaction.user.id && t.open).length >= supportbot.Ticket.TicketsPerUser
) {
return interaction.reply({
embeds: [
{
title: "Too Many Tickets!",
description: `You can't have more than ${supportbot.Ticket.TicketsPerUser} open tickets!`,
color: supportbot.Embed.Colours.Warn,
},
],
ephemeral: true,
});
}
let ticketNumberID = await TicketNumberID.pad();
const TicketSubject = TicketReason || msgconfig.Ticket.InvalidSubject;
const TicketExists = new EmbedBuilder()
.setTitle("Ticket Exists!")
.setDescription(msgconfig.Ticket.TicketExists);
if (
await interaction.guild.channels.cache.find(
(ticketChannel) => ticketChannel.name === `${supportbot.Ticket.Channel}${ticketNumberID}`
)
) {
return await interaction.reply({
embeds: [TicketExists],
ephemeral: true,
});
}
const Staff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
const Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!Staff || !Admin)
return interaction.reply({
content: "Some roles seem to be missing!\nPlease check for errors when starting the bot.",
ephemeral: true,
});
let ticketChannel;
if (supportbot.Ticket.TicketType === "threads") {
const TicketHome = interaction.guild.channels.cache.find(
(c) => c.name === supportbot.Ticket.TicketHome || c.id === supportbot.Ticket.TicketHome
);
const channel = await getChannel(supportbot.Ticket.TicketHome, interaction.guild);
ticketChannel = await channel.threads.create({
name: `${supportbot.Ticket.Channel}${ticketNumberID}`,
type: ChannelType.PrivateThread,
parent: TicketHome,
autoArchiveDuration: 60,
reason: 'support ticket',
});
} else if (supportbot.Ticket.TicketType === "channels") {
const category = interaction.guild.channels.cache.find(
(c) => c.name === supportbot.Ticket.TicketChannelsCategory || c.id === supportbot.Ticket.TicketChannelsCategory
);
if (!category) {
return interaction.reply({
content: "The ticket category does not exist!",
ephemeral: true,
});
}
ticketChannel = await interaction.guild.channels.create({
name: `${supportbot.Ticket.Channel}${ticketNumberID}`,
type: ChannelType.GuildText,
parent: category.id,
reason: 'support ticket',
});
// Move to secondary category if the primary one is full
if (category.children.size >= 50) {
const secondaryCategory = interaction.guild.channels.cache.find(
(c) => c.name === supportbot.Ticket.TicketChannelsCategory2 || c.id === supportbot.Ticket.TicketChannelsCategory2
);
if (secondaryCategory) {
await ticketChannel.setParent(secondaryCategory.id);
} else {
return interaction.reply({
content: "The secondary ticket category does not exist!",
ephemeral: true,
});
}
}
}
// Assign ticket to a user if claiming tickets is enabled
if (supportbot.Ticket.ClaimTickets.Enabled) {
const assignedUserId = await assignTicketToUser(ticketChannel, Staff, Admin, interaction);
if (!assignedUserId) {
const claimChannel = await interaction.guild.channels.cache.get(supportbot.Ticket.ClaimTickets.Channel);
}
}
if (supportbot.Ticket.TicketType === "threads") {
await ticketChannel.members.add(interaction.user.id);
} else if (supportbot.Ticket.TicketType === "channels") {
await ticketChannel.permissionOverwrites.create(interaction.user.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
await ticketChannel.permissionOverwrites.create(Admin, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
await ticketChannel.permissionOverwrites.create(interaction.guild.roles.everyone, {
ViewChannel: false,
});
} }
// Ask questions to the user after the ticket is opened
if (supportbot.Ticket.Questions.Enabled) {
const questions = supportbot.Ticket.Questions.List || [];
if (questions.length > 0) {
await askTicketQuestions(ticketChannel, interaction, questions);
}
}
if (User.roles.cache.has(supportbot.Roles.ModRoles.Blacklisted)) { // Save ticket data
return interaction.reply({ TicketData.tickets.push({
content: msgconfig.Ticket.Blacklisted, id: ticketChannel.id,
ephemeral: true, name: ticketChannel.name,
}); user: interaction.user.id,
} number: ticketNumberID,
reason: TicketSubject,
if (User.roles.cache.has(supportbot.Roles.ModRoles.Muted)) { open: true,
return interaction.reply({ subUsers: [],
content: msgconfig.Ticket.Muted, createdAt: new Date().toISOString(),
ephemeral: true, claimedAt: null,
});
}
let ticketNumberID = await TicketNumberID.pad();
const TicketSubject = interaction.options?.getString("reason") || msgconfig.Ticket.InvalidSubject;
const TicketExists = new EmbedBuilder()
.setTitle("Ticket Exists!")
.setDescription(msgconfig.Ticket.TicketExists);
if (
await interaction.guild.channels.cache.find(
(ticketChannel) => ticketChannel.name === `${supportbot.Ticket.Channel}${ticketNumberID}`
)
) {
return await interaction.reply({
embeds: [TicketExists],
ephemeral: true,
});
}
const Staff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
const Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!Staff || !Admin)
return interaction.reply({
content: "Some roles seem to be missing!\nPlease check for errors when starting the bot.",
ephemeral: true,
}); });
const TicketCategory = interaction.guild.channels.cache.find( fs.writeFileSync(
(c) => c.name === supportbot.Ticket.TicketCategory || c.id === supportbot.Ticket.TicketCategory "./Data/TicketData.json",
); JSON.stringify(TicketData, null, 4),
(err) => {
const channel = await getChannel(supportbot.Ticket.TicketCategory, interaction.guild); if (err) console.error(err);
const ticketChannel = await channel.threads.create({ }
name: `${supportbot.Ticket.Channel}${ticketNumberID}`,
type: ChannelType.PrivateThread,
parent: TicketCategory,
autoArchiveDuration: 60,
reason: 'support ticket',
});
if (supportbot.Ticket.ClaimTickets.Enabled === false) {
const InvStaff = await ticketChannel.send(`<@&${Staff.id}> <@&${Admin.id}>`)
setTimeout(() => {
InvStaff.delete();
}, 2000)
}
// TICKET CLAIMING SYSTEM [START]
if (supportbot.Ticket.ClaimTickets.Enabled) {
const InvStaff1 = await ticketChannel.send(`<@&${Admin.id}>`)
setTimeout(() => {
InvStaff1.delete();
}, 2000)
const claimChannel = await getChannel(
supportbot.Ticket.ClaimTickets.Channel,
interaction.guild
); );
const claimEmbed = new EmbedBuilder() // Notify the user that the ticket was created
.setTitle(msgconfig.Ticket.ClaimTickets.ClaimTitle) const CreatedTicket = new EmbedBuilder()
.setDescription(msgconfig.Ticket.ClaimTickets.ClaimMessage .setDescription(
.replace('%user%', interaction.user.id) msgconfig.Ticket.TicketCreatedAlert.replace(/%ticketauthor%/g, interaction.user.id)
) .replace(/%ticketid%/g, ticketChannel.id)
.setColor(supportbot.Embed.Colours.General); .replace(/%ticketusername%/g, interaction.user.username)
.replace(/%ticketreason%/g, TicketSubject)
)
.setColor(supportbot.Embed.Colours.General);
await interaction.reply({ embeds: [CreatedTicket], ephemeral: true });
const claimButton = new ButtonBuilder() // Create the ticket message
.setCustomId(`claimticket-${ticketChannel.id}`) const TicketMessage = new EmbedBuilder()
.setLabel(supportbot.Ticket.ClaimTickets.ButtonTitle) .setAuthor({
.setStyle(supportbot.Ticket.ClaimTickets.Button); name: msgconfig.Ticket.TicketAuthorTitle.replace(/%ticketauthor%/g, interaction.user.id)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username),
iconURL: interaction.user.displayAvatarURL(),
})
.setTitle(
msgconfig.Ticket.TicketTitle.replace(/%ticketauthor%/g, interaction.user.id)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username)
)
.setDescription(
msgconfig.Ticket.TicketMessage.replace(/%ticketauthor%/g, interaction.user.id)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username)
.replace(/%ticketreason%/g, TicketSubject)
)
.setColor(supportbot.Embed.Colours.General);
const row = new ActionRowBuilder().addComponents(claimButton); if (supportbot.Ticket.TicketReason && TicketReason) {
TicketMessage.addFields(
await claimChannel.send({ { name: "Reason", value: TicketReason, inline: false }
content: `<@&${Staff.id}>`, );
embeds: [claimEmbed],
components: [row]
});
}
// TICKET CLAIMING SYSTEM [END]
if (!department) {
await ticketChannel.members.add(interaction.user.id);
}
TicketData.tickets.push({
id: ticketChannel.id,
name: ticketChannel.name,
user: interaction.user.id,
number: ticketNumberID,
reason: TicketSubject,
open: true,
subUsers: [],
createdAt: new Date().toISOString(),
claimedAt: null, // Will be updated when claimed
});
fs.writeFileSync(
"./Data/TicketData.json",
JSON.stringify(TicketData, null, 4),
(err) => {
if (err) console.error(err);
} }
);
const CreatedTicket = new EmbedBuilder() // Create the control panel
.setDescription( const SelectMenus = new StringSelectMenuBuilder()
msgconfig.Ticket.TicketCreatedAlert.replace(/%ticketauthor%/g, interaction.user.id)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username)
)
.setColor(supportbot.Embed.Colours.General);
await interaction.reply({ embeds: [CreatedTicket], ephemeral: true });
if (supportbot.Ticket.AllowMentions) {
await ticketChannel.send(`${interaction.user}`);
}
const TicketMessage = new EmbedBuilder()
.setAuthor({
name: msgconfig.Ticket.TicketAuthorTitle.replace(/%ticketauthor%/g, interaction.user.id)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username),
iconURL: interaction.user.displayAvatarURL(),
})
.setTitle(
msgconfig.Ticket.TicketTitle.replace(/%ticketauthor%/g, interaction.user.id)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username)
)
.setDescription(
msgconfig.Ticket.TicketMessage.replace(/%ticketauthor%/g, interaction.user.id)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username)
)
.setColor(supportbot.Embed.Colours.General);
const SelectMenus = new StringSelectMenuBuilder()
.setCustomId("ticketcontrolpanel") .setCustomId("ticketcontrolpanel")
.setPlaceholder("Ticket Control Panel") .setPlaceholder("Ticket Control Panel")
.addOptions( .addOptions(
new StringSelectMenuOptionBuilder()
.setLabel("Add User")
.setDescription("Add a user to the ticket.")
.setEmoji(supportbot.SelectMenus.Tickets.AddUserEmoji)
.setValue("ticketadduser"),
new StringSelectMenuOptionBuilder()
.setLabel("Remove User")
.setDescription("Remove a user from the ticket.")
.setEmoji(supportbot.SelectMenus.Tickets.RemoveUserEmoji)
.setValue("ticketremoveuser"),
new StringSelectMenuOptionBuilder() new StringSelectMenuOptionBuilder()
.setLabel("Close") .setLabel("Close")
.setDescription("Close the ticket.") .setDescription("Close the ticket.")
.setEmoji(supportbot.SelectMenus.Tickets.CloseEmoji) .setEmoji(supportbot.SelectMenus.Tickets.CloseEmoji)
.setValue("ticketclose"), .setValue("ticketclose"),
new StringSelectMenuOptionBuilder() new StringSelectMenuOptionBuilder()
.setLabel("Enable Invites") .setLabel("Voice Channel")
.setDescription("Enable invites.") .setDescription("Create a support voice channel.")
.setEmoji(supportbot.SelectMenus.Tickets.EnableInvitesEmoji) .setEmoji(supportbot.SelectMenus.Tickets.VCEmoji)
.setValue("enableinvites"), .setValue("supportvc"),
new StringSelectMenuOptionBuilder() new StringSelectMenuOptionBuilder()
.setLabel("Disable Invites") .setLabel("Archive")
.setDescription("Disable invites.") .setDescription("Archive the ticket.")
.setEmoji(supportbot.SelectMenus.Tickets.DisableInvitesEmoji) .setEmoji(supportbot.SelectMenus.Tickets.ArchiveEmoji)
.setValue("disableinvites"), .setValue("archiveticket"),
...(supportbot.Ticket.TicketType === "threads" ? [
new StringSelectMenuOptionBuilder()
.setLabel("Lock Ticket")
.setDescription("Lock the ticket.")
.setEmoji(supportbot.SelectMenus.Tickets.LockEmoji)
.setValue("lockticket")
] : []),
new StringSelectMenuOptionBuilder()
.setLabel("Rename Ticket")
.setDescription("Rename the ticket.")
.setEmoji(supportbot.SelectMenus.Tickets.RenameEmoji)
.setValue("renameticket")
); );
const row2 = new ActionRowBuilder().addComponents(SelectMenus); if (supportbot.Ticket.TicketType === "threads") {
SelectMenus.addOptions(
if (!department) { new StringSelectMenuOptionBuilder()
try { .setLabel("Enable Invites")
let buttons = supportbot.Departments.map((x) => .setDescription("Enable invites.")
new ButtonBuilder() .setEmoji(supportbot.SelectMenus.Tickets.EnableInvitesEmoji)
.setCustomId("Department" + supportbot.Departments.indexOf(x)) .setValue("enableinvites"),
.setLabel(x.title) new StringSelectMenuOptionBuilder()
.setStyle(x.color) .setLabel("Disable Invites")
.setEmoji(x.emoji) .setDescription("Disable invites.")
); .setEmoji(supportbot.SelectMenus.Tickets.DisableInvitesEmoji)
.setValue("disableinvites"),
if (supportbot.Ticket.AllowMentions) { );
await ticketChannel.send("<@&" + supportbot.Ticket.RoleMention + ">");
} }
const row = new ActionRowBuilder().addComponents(buttons); const row2 = new ActionRowBuilder().addComponents(SelectMenus);
const m = await ticketChannel.send({
embeds: [TicketMessage],
components: [row2],
});
try {
const filter = (i) => i.user.id === interaction.user.id;
await m.awaitMessageComponent({
filter,
max: 1,
componentType: "BUTTON",
time: supportbot.Ticket.Timeout * 60000,
});
} catch (e) {
if (e.code === "COLLECTOR_ERROR") {
try {
let ticket = TicketData.tickets.findIndex((t) => t.id === ticketChannel.id);
TicketData.tickets[ticket].open = false;
fs.writeFileSync(
"./Data/TicketData.json",
JSON.stringify(TicketData, null, 4),
(err) => {
if (err) console.error(err);
}
);
interaction.user.send(
"Your ticket has timed out. Please open a new one, and select a department."
);
} catch (error) {
console.error("An error occurred while handling collector:", error);
}
return await ticketChannel.delete();
}
}
} catch (error) {
console.error("Error sending message:", error);
}
} else {
await ticketChannel.send({ await ticketChannel.send({
embeds: [TicketMessage], embeds: [TicketMessage],
components: [row2], components: [row2],
}); });
} catch (error) {
console.error("Error in run method:", error);
} }
}, },
}); });
+7 -2
View File
@@ -35,8 +35,13 @@ module.exports = new Command({
}); });
} }
const channelName = panelconfig.Channel; const { getChannel } = interaction.client;
const channel = interaction.guild.channels.cache.find(ch => ch.name === channelName);
const channel = await getChannel(
supportbot.Ticket.TicketHome,
interaction.guild
);
if (!channel) { if (!channel) {
console.log(`[TICKET PANEL] ${channelName} channel not found. Please check your config file.`); console.log(`[TICKET PANEL] ${channelName} channel not found. Please check your config file.`);
return interaction.reply({ return interaction.reply({
-56
View File
@@ -1,56 +0,0 @@
const { EmbedBuilder, ApplicationCommandType } = require("discord.js");
const fs = require("fs");
const yaml = require("js-yaml");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const Command = require("../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.TicketStats.Command,
description: cmdconfig.TicketStats.Description,
type: ApplicationCommandType.ChatInput,
permissions: ["SendMessages"], // Adjust permissions as needed
async run(interaction) {
if (supportbot.Ticket.ClaimTickets.Enabled) {
const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const userId = interaction.user.id;
const ticketsClaimed = TicketData.tickets.filter(t => t.claimedBy === userId);
const ticketsOpen = ticketsClaimed.filter(t => t.open);
const totalTickets = ticketsClaimed.length;
let totalResponseTime = 0;
let responseCount = 0;
ticketsClaimed.forEach(ticket => {
if (ticket.claimedAt && ticket.createdAt) {
const responseTime = new Date(ticket.claimedAt) - new Date(ticket.createdAt);
totalResponseTime += responseTime;
responseCount++;
}
});
const averageResponseTime = responseCount ? totalResponseTime / responseCount : 0;
const averageResponseTimeMinutes = Math.round(averageResponseTime / 60000); // Convert to minutes
const statsEmbed = new EmbedBuilder()
.setTitle(msgconfig.TicketStats.Title)
.setDescription(`> Ticket stats for <@${interaction.user.id}>`)
.setColor(supportbot.Embed.Colours.General)
.addFields(
{ name: msgconfig.TicketStats.OpenTickets, value: `${ticketsOpen.length}`, inline: true },
{ name: msgconfig.TicketStats.TotalTickets, value: `${totalTickets}`, inline: true },
{ name: msgconfig.TicketStats.ResponseTime, value: `${averageResponseTimeMinutes} minutes`, inline: false }
);
await interaction.reply({ embeds: [statsEmbed], ephemeral: true });
}
}
});
+1 -1
View File
@@ -71,7 +71,7 @@ module.exports = new Command({
let url = "https://www.science.co.il/language/Codes.php"; // URL to all available language codes let url = "https://www.science.co.il/language/Codes.php"; // URL to all available language codes
let text = await interaction.options.getString("text"); // Grab the text to translate by the user let text = await interaction.options.getString("text"); // Grab the text to translate by the user
let translatelog = await getChannel( let translatelog = await getChannel(
supportbot.Translate.Log, supportbot.Translate.TranslateLog,
interaction.guild interaction.guild
); // Grab the set logging channel for translations ); // Grab the set logging channel for translations
-137
View File
@@ -1,137 +0,0 @@
// SupportBot | Emerald Services
// User Info Command
const fs = require("fs");
const moment = require("moment");
const Discord = require("discord.js");
const yaml = require("js-yaml");
const supportbot = yaml.load(
fs.readFileSync("./Configs/supportbot.yml", "utf8")
);
const cmdconfig = yaml.load(
fs.readFileSync("./Configs/commands.yml", "utf8")
);
const msgconfig = yaml.load(
fs.readFileSync("./Configs/messages.yml", "utf8")
)
const Command = require("../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.UserInfo.Command,
description: cmdconfig.UserInfo.Description,
type: Discord.ApplicationCommandType.ChatInput,
options: [
{
type: Discord.ApplicationCommandOptionType.User,
name: "user",
description: "The user you want to get info about.",
required: true,
}, // The user input
],
permissions: cmdconfig.UserInfo.Permission,
async run(interaction) {
let disableCommand = true;
const { getRole } = interaction.client;
let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!SupportStaff || !Admin)
return interaction.reply(
"Some roles seem to be missing!\nPlease check for errors when starting the bot."
);
const NoPerms = new Discord.EmbedBuilder()
.setTitle("Invalid Permissions!")
.setDescription(
`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``
)
.setColor(supportbot.Embed.Colours.Warn);
if (
!interaction.member.roles.cache.has(SupportStaff.id) &&
!interaction.member.roles.cache.has(Admin.id)
)
return interaction.reply({ embeds: [NoPerms] });
let user = await interaction.options.getUser("user") || interaction.author; // Grab the mentioned user
let member = await interaction.guild.members.cache.get(user.id) // Check for user and his ID
const UserNotExist = new Discord.EmbedBuilder()
.setTitle("User Not Found!")
.setDescription(
`${msgconfig.Error.UserNotFound}\n\nTry Again:\`/${cmdconfig.UserInfoCommand} <@!User_ID> or @User\``
)
.setColor(supportbot.ErrorColour);
if (!user) return interaction.reply({ embeds: [UserNotExist] });
// Flags a user/bot can have
const flags = {
DISCORD_EMPLOYEE: 'Discord Employee',
DISCORD_PARTNER: 'Discord Partner',
BUGHUNTER_LEVEL_1: 'Bug Hunter (Level 1)',
BUGHUNTER_LEVEL_2: 'Bug Hunter (Level 2)',
HYPESQUAD_EVENTS: 'HypeSquad Events',
HOUSE_BRAVERY: 'House of Bravery',
HOUSE_BRILLIANCE: 'House of Brilliance',
HOUSE_BALANCE: 'House of Balance',
EARLY_SUPPORTER: 'Early Supporter',
TEAM_USER: 'Team User',
SYSTEM: 'System',
VERIFIED_BOT: 'Verified Bot',
VERIFIED_DEVELOPER: 'Verified Bot Developer'
};
// Status message with icon
let status = {
online: "🟢Online",
idle: "🟡Idle",
dnd: "🔴Do Not Disturb",
offline: "⚫Offline"
};
const userFlags = user.flags.toArray();
// Embed containing the userinfo
let userembed = new Discord.EmbedBuilder()
.setTitle(`User Info for User: ${user.tag}`)
.setThumbnail(member.displayAvatarURL({ dynamic: true, size: 512 }))
.setColor(member.roles.highest.hexColor)
.addFields(
{ name: "Name:", value: `${user.tag}\n<@${member.id}>`, inline: true },
{ name: "ID:", value: `${member.id}`, inline: true },
{ name: "Is Bot:", value: user.bot ? "Yes" : "No", inline: true },
{ name: "House/Badge:", value: `${userFlags.length ? userFlags.map(flag => flags[flag]).join("\n") : "None"}`, inline: true },
{ name: "Current Status:", value: `${status[member.presence ? member.presence.status : "offline"]}`, inline: true },
{ name: "Avatar URL:", value: `[Click Here](${member.displayAvatarURL()})`, inline: true },
{ name: "\u200b", value: "\u200b", inline: false}
)
.addFields(
{ name: "Creation Date:", value: `${moment(user.createdAt).format("MMMM Do YYYY, h:mm:ss a")}\n> ${moment(user.createdAt).startOf("day").fromNow()}`, inline: true },
{ name: "Joined Date:", value: `${moment(member.joinedAt).format("MMMM Do YYYY, h:mm:ss a")}\n> ${moment(member.joinedAt).startOf("day").fromNow()}`, inline: true },
{ name: "Roles Total:", value: `${member.roles.cache.size -1}`, inline: false },
{ name: "Highest Role:", value: `${member.roles.highest.id === interaction.guild.id ? "None" : member.roles.highest.toString()}`, inline: true },
{ name: "Roles:", value: `${member.roles.cache.map(r => r).join(" ").replace("@everyone", " ") || "None"}`, inline: false },
)
.setFooter({ text: supportbot.Footer })
.setTimestamp();
// Send embed [userembed]
await interaction.reply({ embeds: [userembed] }).catch((err) => console.error("An error has occured: ", err));
},
})
/**
* @INFO
* UserInfo command by Sypher#3415 | 321332200668790786
* @INFO
* Solely created for SupportBot | https://github.com/Emerald-Services/SupportBot/
**/
+57 -15
View File
@@ -7,11 +7,11 @@
# SupportBot created by Emerald Services # SupportBot created by Emerald Services
# Installed with MIT License # Installed with MIT License
# #
# Discord Support: https://dsc.gg/emerald-dev # Discord Support: https://discord.gg/emeraldsrv
# Official Documentation: https://emeraldsrv.dev/wiki # Official Documentation: https://docs.emeraldsrv.com
# Third-Party Documentation: https://emeraldsrv.dev/third-party # Addons Marketplace: https://community.emeraldsrv.com
# #
# Command Configuration # Commands Configuration (commands.yml)
Help: Help:
Command: "help" Command: "help"
@@ -19,12 +19,6 @@ Help:
Permission: ["SendMessages"] Permission: ["SendMessages"]
Enabled: true Enabled: true
EnableAI:
Command: "enableai"
Description: "Talk with SupportBot"
Permission: ["SendMessages"]
Enabled: true
Info: Info:
Command: "info" Command: "info"
Description: "Get information about the **ServerName**." Description: "Get information about the **ServerName**."
@@ -37,9 +31,9 @@ TicketPanel:
Permission: ["Administrator"] Permission: ["Administrator"]
Enabled: true Enabled: true
TicketStats: Profile:
Command: "ticketstats" Command: "profile"
Description: "Shows staff members their ticket statistics" Description: "Shows the profile for a discord member."
Permission: ["SendMessages"] Permission: ["SendMessages"]
# Command is automatically disabled when the TicketClaims is disabled. # Command is automatically disabled when the TicketClaims is disabled.
@@ -49,9 +43,21 @@ OpenTicket:
Permission: ["SendMessages"] Permission: ["SendMessages"]
Enabled: true Enabled: true
Add: AddUser:
Command: "add" Command: "add"
Description: "Add a user to the ticket." Description: "Add a user to a support ticket."
Permission: ["SendMessages"]
Enabled: true
ForceAddUser:
Command: "forceadd"
Description: "Add a user to a support ticket by bypassing the invite system."
Permission: ["SendMessages"]
Enabled: true
RemoveUser:
Command: "remove"
Description: "Remove a user from a support ticket."
Permission: ["SendMessages"] Permission: ["SendMessages"]
Enabled: true Enabled: true
@@ -91,6 +97,42 @@ Ping:
Permission: ["SendMessages"] Permission: ["SendMessages"]
Enabled: true Enabled: true
# ---------------------------------------
# Bot Moderator Commands
# ---------------------------------------
Mod:
Command: "mod"
Description: "Execute a moderation action."
Permission: ["BanMembers", "KickMembers"]
Enabled: true
TicketBlacklist:
Command: "ticket-blacklist"
Description: "Blacklist a user from creating Tickets."
Add:
Command: "add"
Description: "Add a user to the blacklist."
Remove:
Command: "remove"
Description: "Remove a user from the blacklist."
View:
Command: "view"
Description: "View all blacklisted users."
# ---------------------------------------
# Bot Administrator Commands
# ---------------------------------------
Settings:
Command: "settings"
Description: "Update bot settings."
Permission: ["Administrator"]
Enabled: true
# --------------------------------------- # ---------------------------------------
# Command Description Configuration # Command Description Configuration
# --------------------------------------- # ---------------------------------------
+197 -40
View File
@@ -7,24 +7,32 @@
# SupportBot created by Emerald Services # SupportBot created by Emerald Services
# Installed with MIT License # Installed with MIT License
# #
# Discord Support: https://dsc.gg/emerald-dev # Discord Support: https://discord.gg/emeraldsrv
# Official Documentation: https://emeraldsrv.com/wiki # Official Documentation: https://docs.emeraldsrv.com
# Third-Party Documentation: https://emeraldsrv.com/third-party # Addons Marketplace: https://community.emeraldsrv.com
# #
# Messages Configuration # Messages Configuration (messages.yml)
# ----------------------------
# Info Command
# ----------------------------
Info: Info:
Title: "About Us" Title: "About Us"
Description: "SupportBot is an open-sourced support system for discord. It's completely free to use with high amounts of customization to match your own brand." Description: "SupportBot is an open-sourced support system for discord. It's completely free to use with high amounts of customization to match your own brand."
Button: "Visit Site" Button: "Visit Site"
URL: "https://emeraldsrv.com" URL: "https://docs.emeraldsrv.com"
Colour: "#f74343" Colour: "#f74343"
# ----------------------------
# Welcome/Leave Messages
# ----------------------------
Welcome: Welcome:
Embed: Embed:
Colour: "#f74343" Colour: "#f74343"
Title: "Welcome to ServerName" Title: "Welcome to ServerName"
Message: "> :smile: %joined_user% has just landed!" Message: ":smile: %joined_user% has just landed!"
Thumbnail: "BOT" Thumbnail: "BOT"
ImageEnabled: false ImageEnabled: false
@@ -34,18 +42,26 @@ Leave:
Embed: Embed:
Colour: "#f74343" Colour: "#f74343"
Title: "Goodbye!" Title: "Goodbye!"
Message: "> :slight_frown: %joined_user% has recently dropped out!" Message: ":slight_frown: %joined_user% has recently dropped out!"
Thumbnail: "BOT" Thumbnail: "BOT"
ImageEnabled: false ImageEnabled: false
ImageURL: "https://animagelink.png/" ImageURL: "https://animagelink.png/"
# ----------------------------
# Suggestion Messages
# ----------------------------
Suggestions: Suggestions:
Sent_Title: "Suggestion Sent!" Sent_Title: "Suggestion Sent!"
Sent: "> :white_check_mark: **You have successfully sent a suggestion.**" Sent: ":white_check_mark: **You have successfully sent a suggestion.**"
NoSelfVote: "> :x: **You cannot vote on your own suggestion.**" NoSelfVote: ":x: **You cannot vote on your own suggestion.**"
RemoveVoteButton: "Remove Vote" RemoveVoteButton: "Remove Vote"
# ----------------------------
# Ticket Messages
# ----------------------------
Ticket: Ticket:
# Available Placeholders: # Available Placeholders:
@@ -56,47 +72,82 @@ Ticket:
TicketAuthorTitle: "%ticketusername%'s Ticket" TicketAuthorTitle: "%ticketusername%'s Ticket"
TicketTitle: "Support Ticket" TicketTitle: "Support Ticket"
TicketMessage: "> Hello <@%ticketauthor%>, Thank you for creating a support ticket\nPlease wait patiently whilst a member of our team reaches out to you." TicketMessage: "Hello <@%ticketauthor%>, Thank you for creating a support ticket\nPlease wait patiently whilst a member of our team reaches out to you."
TicketThumbnail: "https://images.emojiterra.com/google/android-pie/512px/1f39f.png" TicketThumbnail: "https://images.emojiterra.com/google/android-pie/512px/1f39f.png"
TicketCreatedAlert: "> :ticket: Ticked Created: <#%ticketid%>" TicketCreatedAlert: ":ticket: Ticked Created: <#%ticketid%>"
TicketExists: "> ❌ Ticket already exists" TicketExists: "❌ Ticket already exists"
TicketClaim: "> :white_check_mark: Ticket claimed by" #The user who claimed the ticket will be added to the end of this message TicketClaim: ":white_check_mark: Ticket claimed by" #The user who claimed the ticket will be added to the end of this message
TicketUnclaim: "> :white_check_mark: Ticket unclaimed by" #The user who claimed the ticket will be added to the end of this message TicketUnclaim: ":white_check_mark: Ticket unclaimed by" #The user who claimed the ticket will be added to the end of this message
DisableInvites: "> 🔒 Invites have been disabled for this ticket." TicketArchived: ":white_check_mark: Ticket archived."
EnableInvites: "> 🔓 Invites have been enabled for this ticket." TicketUnarchived: ":white_check_mark: Ticket unarchived."
TicketLocked: ":lock: Ticket is locked."
DisableInvites: "🔒 Invites have been disabled for this ticket."
EnableInvites: "🔓 Invites have been enabled for this ticket."
# Available Placeholders: # Available Placeholders:
# <@%user%> - Tags the user who was added to the ticket. # <@%user%> - Tags the user who was added to the ticket.
AddUserToTicket_Title: "➕ Add User!" Blacklisted: ":x: You are not allowed to open a ticket"
AddUserToTicket: "> To add a user to this ticket, Tag them." Muted: ":x: You are `Muted` and therefore not allowed to open any tickets"
InvalidSubject: "No Reason Provided!"
RemovedUserFromTicket_Title: "➖ Removed User!" ConfirmClose: "Please confirm closing the ticket by clicking the button below."
RemovedUserFromTicket: "> <@%user%> has been `Removed` from this ticket" CloseTimeout: "Ticket close confirmation timed out."
Blacklisted: "> :x: You are not allowed to open a ticket" RenamedSuccess: "🏷️ Ticket renamed successfully!"
Muted: "> :x: You are `Muted` and therefore not allowed to open any tickets"
InvalidSubject: "> No Reason Provided!"
TranscriptSavedMessage: "📝 **Transcript Saved!**"
TicketLockMessage: "❌ **Ticket Locked!**"
TicketDeleteMessage: "⚠ **Ticket Deleting!**"
ClosingTicket: "> :warning: You have requested to close your support ticket."
ConfirmClose: "> Please confirm closing the ticket by clicking the button below."
CloseTimeout: "> Ticket close confirmation timed out."
ClaimTickets: ClaimTickets:
ClaimTitle: "Incoming Support Request!" ClaimTitle: "Incoming Support Request!"
ClaimMessage: "> A new ticket has been created by <@!%user%>. Click the button below to claim this ticket." ClaimMessage: "A new ticket has been created by <@!%user%>. Click the button below to claim this ticket."
ClaimMessage_Edit: "> <#%channel%> was claimed by <@!%user%>." ClaimMessage_Edit: "<#%channel%> was claimed by <@!%user%>."
ClaimedTitle: "Ticket Claimed!" ClaimedTitle: "Ticket Claimed!"
Claimed: "> You have successfully claimed this ticket. <#%channel%>" Claimed: "You have successfully claimed this ticket. <#%channel%>"
NoPermsToClaimTitle: "Error, no permissions!" NoPermsToClaimTitle: "Error, no permissions!"
NoPermsToClaim: "> :x: **Err!** You do not permissions to claim this ticket." NoPermsToClaim: ":x: **Err!** You do not permissions to claim this ticket."
ReassigningTitle: "Reassigning!..."
ReassigningMessage: "<@%user%> did not claim the ticket. **Reassigning...**"
TicketQuestions:
Details:
Title: "Ticket Details"
Description: "Questions by <@%user%>" # Use <@%user%> to display the username.
# ----------------------------
# Review Messages
# ----------------------------
ReviewSystem:
Rate:
Title: "Review us!"
Description: "Please rate our support for this ticket (1-5 stars)."
Stars:
One: "⭐ 1 Star"
Two: "⭐⭐ 2 Stars"
Three: "⭐⭐⭐ 3 Stars"
Four: "⭐⭐⭐⭐ 4 Stars"
Five: "⭐⭐⭐⭐⭐ 5 Stars"
Comment:
Title: "Write a comment."
Description: "Write a comment about your experience."
ReviewEmbed:
Title: "Ticket Review"
CommentTitle: "Comment"
RatingTitle: "Rating"
ReviewedStaffTitle: "Reviewed Staff:"
ReviewedByTitle: "Reviewed By:"
ReviewEmoji: "⭐"
Color: "#f7a943"
# ----------------------------
# Profile Messages
# ----------------------------
TicketStats: TicketStats:
OpenTickets: "✅ Open Tickets" OpenTickets: "✅ Open Tickets"
@@ -105,12 +156,118 @@ TicketStats:
Title: "Your Ticket Stats" Title: "Your Ticket Stats"
# ----------------------------
# Mod Command Messages
# ----------------------------
Mod:
TicketBlacklist:
Add:
Description: "Add a user to the ticket blacklist"
UserDescription: "User to blacklist"
AlreadyBlacklisted: ":x: {userTag} is already blacklisted."
Success: ":white_check_mark: {userTag} has been added to the blacklist. They can no longer use the ticket system."
Remove:
Description: "Remove a user from the ticket blacklist"
UserDescription: "User to remove from blacklist"
NotBlacklisted: ":x: {userTag} is not blacklisted."
Success: ":white_check_mark: {userTag} has been removed from the blacklist. They can now use the ticket system again."
View:
Description: "View the list of all blacklisted users"
NoBlacklistedUsers: ":information_source: There are currently no users on the blacklist."
# ----------------------------
# Logging Messages
# ----------------------------
TicketLog:
Title: "Ticket Log"
Colour: "#0cc559"
TicketBlacklistLog:
Title: "Ticket Blacklist Log"
Colour: "#0cc559"
# ----------------------------
# Misc Messages
# ----------------------------
AddUser:
NotInTicket_Title: "Ticket Invitation"
NotInTicket_Description: "This command can only be used within a ticket thread."
Invite_Title: "User Invited"
Invite_Description: "%username% has been invited to join the ticket."
DM_Title: "Ticket Invitation"
DM_Description: "You have been invited to join the ticket %channelname%. Do you accept?"
Accept_Label: "Accept"
Reject_Label: "Reject"
ViewTicket_Label: "Go to Ticket"
Accepted_Title: "Invitation Accepted"
Accepted_Message: "You have been added to the ticket."
AddedToTicket_Title: "Added to Ticket"
AddedToTicket_Description: "You have been added to the ticket %channel_link%."
# Added Message for inside the ticket, this informs everyone inside the ticket that the user has been added.
Added_Title: "User Added"
Added_Description: "Successfully added **%username%** to the ticket."
Declined_Title: "Invitation Declined"
Declined_Message: "You have declined the invitation."
NoResponse_Title: "No Response"
NoResponse_Message: "You did not respond in time. Please contact staff if you wish to join the ticket."
Error_Title: "Error"
Error_Adding: "There was an error adding you to the ticket. Please try again."
DM_Error_Title: "Error"
DM_Error_Description: "Unable to send a DM to %username%. Please ensure your DMs are enabled."
Unexpected_Error_Title: "Error"
Unexpected_Error_Description: "An unexpected error occurred: %error%"
RemoveUser:
NotInTicket_Title: "Error"
NotInTicket_Description: "This command can only be used in ticket threads."
Removed_Title: "User Removed"
Removed_Message: "The user has been successfully removed from the ticket."
RemovedFromTicket_Title: "Removed from Ticket"
RemovedFromTicket_Description: "You have been removed from the ticket channel %channel_link%."
Error_Title: "Error"
Error_Removing: "There was an error removing the user from the ticket."
ForceAddUser:
NotInTicket_Title: "Not a Ticket"
NotInTicket_Description: "This command can only be used within a ticket thread."
Added_Title: "User Added"
Added_Description: "Successfully added %username% to the ticket."
AddedToTicket_Title: "You have been added to a Ticket"
AddedToTicket_Description: "You have been added to the ticket channel [%channel_link%]."
Error_Title: "Error"
Error_Adding: "There was an error adding the user to the ticket."
Error: Error:
IncorrectPerms: "> :x: **Err!** You do not have the correct permissions to use this." IncorrectPermissions: ":x: **Error!** You do not have the correct permissions to use this command."
InvalidChannel: "> :x: **Err!** Channel not found. Please create it!" MissingChannel: ":x: **Error!** Required channel not found. Please ensure it is set up correctly!"
UserNotFound: "> :x: **Err!** This user doesn't exist, Are they in this server?" UserNotFound: ":x: **Error!** Specified user not found. Please check if they are in this server."
NoValidTicket: "> :warning: You cannot execute this command here." CommandNotAllowedHere: ":warning: This command cannot be used in this channel."
TicketExists: "> :warning: This ticket cannot be opened, It already exists!" EntryAlreadyExists: ":warning: Cannot complete the action, as the entry already exists."
EntryDoesNotExist: ":warning: Cannot complete the action, as the entry does not exist."
ActionFailed: ":x: **Error!** An error occurred while processing your request. Please try again later."
+2 -3
View File
@@ -10,13 +10,12 @@ Enabled: true
General: General:
Name: "SupportBot AI" Name: "SupportBot AI"
# What would you like to call your AI # What would you like to call your AI
Model: "gpt-4o" Model: "gpt-4o-mini"
# You can see a list of OpenAI Models here: https://platform.openai.com/docs/models # You can see a list of OpenAI Models here: https://platform.openai.com/docs/models
Tokens: 400 Tokens: 400
# Max tokens you can set is 400 # Max tokens you can set is 400
# For more information about tokens, go to https://openai.com/api/pricing/ # For more information about tokens, go to https://openai.com/api/pricing/
OpenAI_Key: "OPEN_AI_API_KEY" OpenAI_Key: "OPEN_AI_API_KEY"
# To create an OpenAI API key, go to https://platform.openai.com/api-keys # To create an OpenAI API key, go to https://platform.openai.com/api-keys
@@ -26,7 +25,7 @@ General:
PastebinAPI_URL: "https://pastebin.com/api/api_post.php" PastebinAPI_URL: "https://pastebin.com/api/api_post.php"
Channels: Channels:
AIChannel: "0000000000000000000" AIChannel: "AI_BOT_CHANNEL"
# This is the channel where AI will respond in without needing to mention the bot. # This is the channel where AI will respond in without needing to mention the bot.
Embed: Embed:
+127 -54
View File
@@ -4,15 +4,16 @@
# <___/`___|| _/| _/\___/|_| |_| |___/\___/ |_| # <___/`___|| _/| _/\___/|_| |_| |___/\___/ |_|
# |_| |_| # |_| |_|
# #
# SupportBot created by Emerald Development # SupportBot created by Emerald Services
# Installed with MIT License # Installed with MIT License
# #
# Discord Support: https://dsc.gg/emerald-dev # Discord Support: https://discord.gg/emeraldsrv
# Official Documentation: https://github.com/Emerald-Services/SupportBot/wiki # Official Documentation: https://docs.emeraldsrv.com
# Third-Party Documentation: COMING SOON # Addons Marketplace: https://community.emeraldsrv.com
#
# SupportBot Configuration (supportbot.yml)
SupportBot_Version: "v8.0" SupportBot_Version: "v8.3"
# Do not change the version number
# ------------------------- # -------------------------
# General # General
@@ -20,11 +21,10 @@ SupportBot_Version: "v8.0"
General: General:
Name: "SupportBot" Name: "SupportBot"
Token: "BOT_TOKEN_HERE" Token: "BOT_TOKEN"
# To get your Discord Bot Token, Go to https://discord.com/developers/applications
Addons: Addons:
Enabled: false Enabled: true
# ------------------------- # -------------------------
# Activity # Activity
@@ -33,9 +33,16 @@ General:
Activity: Activity:
Status: "SupportBot v8" Status: "SupportBot v8"
Type: "Competing" Type: "Competing"
# The Type can either be Playing, Watching, Listening or Competing
StreamingURL: "https://www.twitch.tv/YOUR_TWITCH_NAME" StreamingURL: "https://www.twitch.tv/YOUR_TWITCH_NAME"
# Set The Type to "Streaming" for a streaming status!
# Available Types:
# Playing
# Watching
# Listening
# Competing
# Streaming (URL required)
# You can also update the activity via Discord using: /settings
# ------------------------- # -------------------------
# Embeds # Embeds
@@ -43,13 +50,15 @@ Activity:
Embed: Embed:
Colours: Colours:
General: 0x0cc559 # 0x<hexcode> General: 0x0cc559
Success: 0x04b545 # 0x<hexcode> Success: 0x04b545
Error: 0xd13030 # 0x<hexcode> Error: 0xd13030
Warn: 0xfca117 # 0x<hexcode> Warn: 0xfca117
Footer: "SupportBot | Developed by Emerald Development" Footer: "SupportBot | Developed by Emerald Development"
# Use 0x<hexcode> to change the colour of the embeds.
# ------------------------- # -------------------------
# Roles # Roles
# ------------------------- # -------------------------
@@ -57,15 +66,17 @@ Embed:
Roles: Roles:
AutoRole: AutoRole:
Enabled: true Enabled: true
Role: "Member" Role: "ROLE_ID"
StaffMember: StaffMember:
Staff: "Staff" Staff: "ROLE_ID"
Admin: "Admin" Admin: "ROLE_ID"
Moderator: "ROLE_ID"
ModRoles: Mod:
Muted: "Muted" AllowSupportStaff: false
Blacklisted: "Blacklisted"
# Allow Support Staff to use the /mod command. (More in detail customisation in commands.yml)
# ---------------------------- # ----------------------------
# System Messages # System Messages
@@ -73,18 +84,18 @@ Roles:
Welcome: Welcome:
Enabled: true Enabled: true
Channel: "🚨┃join-leave-logs" Channel: "JOIN_CHANNEL_ID"
Leave: Leave:
Enabled: true Enabled: true
Channel: "🚨┃join-leave-logs" Channel: "JOIN_CHANNEL_ID"
# ---------------------------- # ----------------------------
# Suggestions # Suggestions
# ---------------------------- # ----------------------------
Suggestions: Suggestions:
Channel: "📗┃suggestions" Channel: "SUGGESTIONS_CHANNEL_ID"
UpvoteEmoji: "✅" UpvoteEmoji: "✅"
DownvoteEmoji: "❌" DownvoteEmoji: "❌"
@@ -92,6 +103,7 @@ Suggestions:
DownvoteTitle: "No" DownvoteTitle: "No"
OwnSuggestion: true OwnSuggestion: true
# If false this will not allow the user to vote for a suggestion they created # If false this will not allow the user to vote for a suggestion they created
# If true this will allow the user to vote for a suggestion they created # If true this will allow the user to vote for a suggestion they created
@@ -118,44 +130,78 @@ Suggestions:
# Tickets # Tickets
# ------------------------- # -------------------------
VoiceTickets:
Name: "%username%'s VC"
Category: "VOICEE_CATEGORY_ID"
Ticket: Ticket:
TicketCategory: "📗┃tickets" #Where tickets normally go with the /ticket command TicketType: "channels"
# This is the channel name for the ticket threads to be created, NOT A DISCORD CATEGORY!
# Available TicketTypes:
# channels - Tickets will be created as channels in the TicketChannelsCategory
# threads - Tickets will be created as threads through the "TicketHome" channel.
TicketHome: "TICKETHOME_CHANNEL_ID"
# What is TicketHome?
# - Channel where the Ticket creation panel is sent.
# - Channel where ticket threads are made. (only if ticket threads is enabled above.)
# For TicketType: channels
TicketChannelsCategory: "TICKETCHANNEL_CATEGORY_ID"
TicketChannelsCategory2: "TICKETCHANNEL_BACKUP_CATEGORY_ID" # This will be used if the first category has reached the max channel limit.
TicketArchiveCategory: "ARCHIVE_CATEGORY_ID"
# ---
Channel: "ticket-" # The prefix for ticket channel names Channel: "ticket-" # The prefix for ticket channel names
TicketsPerUser: 100 TicketsPerUser: 100
DMTranscripts: true DMTranscripts: true
TicketReason: true TicketReason: true
# If ticket reason is set to false, It will not prompt to create a reason # If ticket reason is set to false, It will not prompt to create a reason
TicketSubject: "description" TicketSubject: "description"
# Available TicketSubjects:
# "description" will display the ticket reason within the channel description # "description" will display the ticket reason within the channel description
# "embed" will display the ticket reason within the ticket embed # "embed" will display the ticket reason within the ticket embed
Timeout: 5 Timeout: 5
# Set to true to allow all staff to access the ticket # Set to true to allow all staff to access the ticket
Log:
TicketLog: "🚨┃ticket-data"
TicketLog_Title: "Ticket Closed"
DisableTicketLogChannel: false
TranscriptLog: "🚨┃ticket-data"
TranscriptTitle: "Ticket Transcript"
Close: Close:
StaffOnly: true StaffOnly: true
# Set to false is anyone is able to close a ticket. # Set to false is anyone is able to close a ticket.
Confirmation_Button: "Confirm Close" Confirmation_Button: "Confirm Close"
Confirmation_Emoji: "🗑️"
Confirmation_Style: "2"
Title: "Are you sure you want to close this ticket?" Title: "Are you sure you want to close this ticket?"
# Available Button Styles:
# 1 - Blurple
# 2 - Grey
# 3 - Green
# 4 - Red
Invites: Invites:
StaffOnly: true StaffOnly: true
ClaimTickets: ClaimTickets:
Enabled: true Enabled: true
Channel: "1249723473757077624" Channel: "CLAIM_TICKETS_CHANNEL_ID"
# Channel name or Channel ID # Channel where the ticket assignments are sent for staff to claim.
ClaimAcceptTime: 120000
# How long the assigned user has to accept the ticket before it gets re-assigned to someone else.
# 120000 = 2 Minutes
# 180000 = 3 Minutes
# 240000 = 4 Minutes
# 300000 = 5 Minutes
# Time in Mliseconds
ButtonTitle: "Claim Ticket" ButtonTitle: "Claim Ticket"
ButtonEmoji: "🎫"
Button: "1" Button: "1"
# Available Button Styles # Available Button Styles
@@ -164,33 +210,52 @@ Ticket:
# 3 - Green # 3 - Green
# 4 - Red # 4 - Red
ReviewSystem:
Enabled: true
Channel: "REVIEW_CHANNEL_ID"
UseModal: true
# ------------------------- Questions:
# Departments - NOT IN THIS UPDATE Enabled: true # Set to false to disable ticket questions
# ------------------------- List:
- "Please provide your account email?"
- "What is the nature of your issue?"
- "Have you tried resolving the issue yourself?"
Departments: Log:
- title: "Create Ticket" TicketDataLog: "TICKET_DATA_LOG_ID"
role: "Staff" TickeDataTitle: "Ticket Data"
color: "2" TicketBlacklistLog: "TICKET_BLACKLIST_LOG_ID"
emoji: "🎫"
category: "TICKETS"
# ------------------------- # -------------------------
# Buttons # Buttons
# ------------------------- # -------------------------
Buttons: Buttons:
General:
Delete: "🗑️"
DeleteStyle: "4"
Voice:
TicketDeleteText: "Delete Voice Channel"
Tickets: Tickets:
ClaimEmoji: "🧑‍💻" ClaimEmoji: "🧑‍💻"
ClaimStyle: "1" ClaimStyle: "1"
Unarchive: "Unarchive Ticket"
Unarchive_Emoji: "♻️"
Unarchive_Style: "2"
Unlock: "Unlock Ticket"
Unlock_Emoji: "🔓"
Unlock_Style: "2"
# Available Button Styles # Available Button Styles
# 1 - Blurple # 1 - Blurple
# 2 - Grey # 2 - Grey
# 3 - Green # 3 - Green
# 4 - Red # 4 - Red
# ------------------------- # -------------------------
# Select Menus # Select Menus
# ------------------------- # -------------------------
@@ -198,19 +263,27 @@ Buttons:
SelectMenus: SelectMenus:
Tickets: Tickets:
PanelEmoji: "⚙️" PanelEmoji: "⚙️"
CloseEmoji: "🗑️" CloseEmoji: "🗑️"
ArchiveEmoji: "♻️" ArchiveEmoji: "♻️"
LockTicketEmoji: "🔒" LockEmoji: "🔐"
UnLockTicketEmoji: "🔓" RenameEmoji: "🏷️"
EnableInvitesEmoji: "🔒" VCEmoji: "🔊"
DisableInvitesEmoji: "🔓"
AddUserEmoji: "➕"
RemoveUserEmoji: "➖"
EnableInvitesEmoji: "📤"
DisableInvitesEmoji: "📥"
# ------------------------- # -------------------------
# Misc # Logging
# ------------------------- # -------------------------
MessageDelete:
Channel: "MESSAGE_DELETE_CHANNEL_ID""
Colour: "#d92525"
MessageUpdate:
Channel: "MESSAGE_UPDATE_CHANNEL_ID"
Colour: "#f2a024"
Translate: Translate:
Log: "🚨┃logs" TranslateLog: "TRANSLATE_LOG_ID"
+5 -5
View File
@@ -7,8 +7,11 @@
# SupportBot created by Emerald Services # SupportBot created by Emerald Services
# Installed with MIT License # Installed with MIT License
# #
# Discord Support: https://emeraldsrv.dev/discord # Discord Support: https://discord.gg/emeraldsrv
# Community Resources: https://emeraldsrv.dev/resources # Official Documentation: https://docs.emeraldsrv.com
# Addons Marketplace: https://community.emeraldsrv.com
#
# Ticket Creation Panel Configuration (ticket-panel.yml)
# ------------------------------- # -------------------------------
# Ticket Panel Configuration # Ticket Panel Configuration
@@ -20,9 +23,6 @@ Panel: true
# Ticket Panel Message Configuration #2b2d31 # Ticket Panel Message Configuration #2b2d31
# ---------------------------------------- # ----------------------------------------
Channel: "📗┃tickets"
# The channel used to create the ticket panel, Use command /ticketpanel to generate it.
PanelTitle: "SupportBot" PanelTitle: "SupportBot"
PanelColour: "#0cc559" PanelColour: "#0cc559"
+3
View File
@@ -0,0 +1,3 @@
{
"blacklistedUsers": []
}
+1
View File
@@ -0,0 +1 @@
{}
+1
View File
@@ -0,0 +1 @@
{}
+1 -3
View File
@@ -1,3 +1 @@
{ {}
}
+5
View File
@@ -0,0 +1,5 @@
{
"bio": "",
"timezone": "",
"clockedIn": false
}
+1
View File
@@ -0,0 +1 @@
{}
+1 -1
View File
@@ -1,4 +1,4 @@
{ {
"id": 0, "id": 0,
"TicketPanelID": "0000000000000000000" "TicketPanelID": "000000000000000"
} }
+20 -19
View File
@@ -19,7 +19,8 @@ const msgconfig = yaml.load(
module.exports = new Event("guildMemberAdd", async (client, member, interaction, guild) => { module.exports = new Event("guildMemberAdd", async (client, member, interaction, guild) => {
if (supportbot.Welcome.Enabled) { if (supportbot.Welcome.Enabled) {
const WelcomeChannel = member.guild.channels.cache.find(channel => channel.name === supportbot.Welcome.Channel) const WelcomeChannel = member.guild.channels.cache.get(supportbot.Welcome.Channel) ||
member.guild.channels.cache.find(channel => channel.name === supportbot.Welcome.Channel);
const WelcomeEmbed = new Discord.EmbedBuilder() const WelcomeEmbed = new Discord.EmbedBuilder()
.setColor(msgconfig.Welcome.Embed.Colour) .setColor(msgconfig.Welcome.Embed.Colour)
@@ -27,29 +28,29 @@ module.exports = new Event("guildMemberAdd", async (client, member, interaction,
.setDescription(msgconfig.Welcome.Embed.Message.replace(/%joined_user%/g, member.user)) .setDescription(msgconfig.Welcome.Embed.Message.replace(/%joined_user%/g, member.user))
.setTimestamp(); .setTimestamp();
if (msgconfig.Welcome.Embed.Thumbnail === "BOT") { if (msgconfig.Welcome.Embed.Thumbnail === "BOT") {
WelcomeEmbed.setThumbnail(client.user.displayAvatarURL()) WelcomeEmbed.setThumbnail(client.user.displayAvatarURL())
} } else if (msgconfig.Welcome.Embed.Thumbnail === "USER") {
WelcomeEmbed.setThumbnail(member.user.displayAvatarURL())
}
if (msgconfig.Welcome.Embed.Thumbnail === "USER") { if (msgconfig.Welcome.Embed.ImageEnabled) {
WelcomeEmbed.setThumbnail(member.user.displayAvatarURL()) WelcomeEmbed.setImage(msgconfig.Welcome.Embed.ImageURL)
} }
if (msgconfig.Welcome.Embed.ImageEnabled) { if (WelcomeChannel) {
WelcomeEmbed.setImage(msgconfig.Welcome.Embed.ImageURL) WelcomeChannel.send({ embeds: [WelcomeEmbed] });
} }
WelcomeChannel.send({ if (supportbot.Roles.AutoRole.Enabled) {
embeds: [WelcomeEmbed] const role = member.guild.roles.cache.get(supportbot.Roles.AutoRole.Role) ||
}) member.guild.roles.cache.find(role => role.name === supportbot.Roles.AutoRole.Role);
if (role) {
if (supportbot.Roles.AutoRole.Enabled) {
var role = member.guild.roles.cache.find(role => role.name === supportbot.Roles.AutoRole.Role);
member.roles.add(role); member.roles.add(role);
} }
}
console.log(`\u001b[32m`, `[+]`, `\u001b[33m`, `${member.user.username} joined the server!`)
console.log(`\u001b[32m`, `[+]`, `\u001b[33m`, `${member.user.username} joined the server!`);
} }
}); });
+16 -16
View File
@@ -19,32 +19,32 @@ const msgconfig = yaml.load(
module.exports = new Event("guildMemberRemove", async (client, member, interaction, guild) => { module.exports = new Event("guildMemberRemove", async (client, member, interaction, guild) => {
if (supportbot.Leave.Enabled) { if (supportbot.Leave.Enabled) {
const LeaveChannel = member.guild.channels.cache.find(channel => channel.name === supportbot.Leave.Channel) const LeaveChannel = member.guild.channels.cache.get(supportbot.Leave.Channel) ||
member.guild.channels.cache.find(channel => channel.name === supportbot.Leave.Channel);
const LeaveEmbed = new Discord.EmbedBuilder() const LeaveEmbed = new Discord.EmbedBuilder()
.setColor(msgconfig.Leave.Embed.Colour) .setColor(msgconfig.Leave.Embed.Colour)
.setTitle(msgconfig.Leave.Embed.Title) .setTitle(msgconfig.Leave.Embed.Title)
.setDescription(msgconfig.Leave.Embed.Message.replace(/%joined_user%/g, member.user)) .setDescription(msgconfig.Leave.Embed.Message.replace(/%left_user%/g, member.user))
.setTimestamp(); .setTimestamp();
if (msgconfig.Leave.Embed.Thumbnail === "BOT") { if (msgconfig.Leave.Embed.Thumbnail === "BOT") {
LeaveEmbed.setThumbnail(client.user.displayAvatarURL()) LeaveEmbed.setThumbnail(client.user.displayAvatarURL());
} } else if (msgconfig.Leave.Embed.Thumbnail === "USER") {
LeaveEmbed.setThumbnail(member.user.displayAvatarURL());
}
if (msgconfig.Leave.Embed.Thumbnail === "USER") { if (msgconfig.Leave.Embed.ImageEnabled) {
LeaveEmbed.setThumbnail(member.user.displayAvatarURL()) LeaveEmbed.setImage(msgconfig.Leave.Embed.ImageURL);
} }
if (msgconfig.Leave.Embed.ImageEnabled) {
LeaveEmbed.setImage(msgconfig.Leave.Embed.ImageURL)
}
if (LeaveChannel) {
LeaveChannel.send({ LeaveChannel.send({
embeds: [LeaveEmbed] embeds: [LeaveEmbed]
}) });
}
console.log(`\u001b[31m`, `[-]`, `\u001b[33m`, `${member.user.username} left the server!`) console.log(`\u001b[31m`, `[-]`, `\u001b[33m`, `${member.user.username} left the server!`);
} }
}); });
+616 -63
View File
@@ -16,7 +16,8 @@ try {
} }
module.exports = new Event("interactionCreate", async (client, interaction) => { module.exports = new Event("interactionCreate", async (client, interaction) => {
if (interaction.type == Discord.InteractionType.ApplicationCommand) {
if (interaction.type === Discord.InteractionType.ApplicationCommand) {
const command = client.commands.find( const command = client.commands.find(
(cmd) => cmd.name.toLowerCase() === interaction.commandName (cmd) => cmd.name.toLowerCase() === interaction.commandName
); );
@@ -24,12 +25,13 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
if (interaction.user.bot || !interaction.guild) return; if (interaction.user.bot || !interaction.guild) return;
const NotValid = new Discord.EmbedBuilder() const NotValid = new Discord.EmbedBuilder()
.setDescription(`:x: \`Invalid Command\` `) .setDescription(`:x: \`Invalid Command\``)
.setColor(supportbot.Embed.Colours.Error); .setColor(supportbot.Embed.Colours.Error);
if (!command) if (!command)
return interaction.reply({ return interaction.reply({
embeds: [NotValid], embeds: [NotValid],
ephemeral: true,
}); });
const permission = interaction.member.permissions.has(command.permissions); const permission = interaction.member.permissions.has(command.permissions);
@@ -43,40 +45,320 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
if (!permission) if (!permission)
return interaction.reply({ return interaction.reply({
embeds: [ValidPerms], embeds: [ValidPerms],
ephemeral: true,
}); });
command.run(interaction);
try {
if (command.name === cmdconfig.OpenTicket.Command && supportbot.Ticket.TicketReason) {
const modal = new Discord.ModalBuilder()
.setCustomId("ticketReasonModal")
.setTitle("Ticket Reason")
.addComponents(
new Discord.ActionRowBuilder().addComponents(
new Discord.TextInputBuilder()
.setCustomId("reasonInput")
.setLabel("Reason")
.setStyle(Discord.TextInputStyle.Paragraph)
.setRequired(true)
)
);
await interaction.showModal(modal);
} else {
console.log("\u001b[32m", "[Executed]", "\u001b[37;1m", `${interaction.user.username} has executed ${command.name}`);
await command.run(interaction);
}
} catch (error) {
console.error("Error executing command:", error);
await interaction.reply({
content: "An error occurred while executing the command.",
ephemeral: true,
});
}
} }
if (interaction.isModalSubmit()) {
if (interaction.customId === "ticketReasonModal") {
const reason = interaction.fields.getTextInputValue("reasonInput");
interaction.customId = `createticket-${reason}`;
const cmd = client.commands.get(cmdconfig.OpenTicket.Command);
if (cmd) {
interaction.reason = reason; // Attach the reason to the interaction object
await cmd.run(interaction);
} else {
console.error("Command not found.");
}
}
}
if (interaction.customId === "renameTicketModal") {
const newTicketName = interaction.fields.getTextInputValue("renameTicket");
// Fixing the ticketChannelId extraction and lookup
const ticketChannelId = interaction.channel.id;
let ticketChannel;
if (supportbot.Ticket.TicketType === "threads") {
ticketChannel = interaction.guild.channels.cache.get(ticketChannelId);
} else if (supportbot.Ticket.TicketType === "channels") {
ticketChannel = interaction.guild.channels.cache.get(ticketChannelId);
}
if (!ticketChannel) {
return interaction.reply({
content: "Ticket channel not found.",
ephemeral: true,
});
}
try {
await ticketChannel.setName(newTicketName);
const renamedsuccess = new Discord.EmbedBuilder()
.setDescription(msgconfig.Ticket.RenamedSuccess)
.setColor(supportbot.Embed.Colours.Success);
return interaction.reply({
embeds: [renamedsuccess],
ephemeral: true,
});
} catch (error) {
console.error("Error renaming the ticket:", error);
return interaction.reply({
content: "There was an error renaming the ticket.",
ephemeral: true,
});
}
}
if (interaction.isStringSelectMenu()) { if (interaction.isStringSelectMenu()) {
if (interaction.customId === "ticketcontrolpanel") { if (interaction.customId === "ticketcontrolpanel") {
const selectedOption = interaction.values[0]; const selectedOption = interaction.values[0];
const { getRole, getChannel } = interaction.client; const { getRole, getChannel } = interaction.client;
switch (selectedOption) { switch (selectedOption) {
case "ticketadduser":
const AddUserThreadTicket = new Discord.EmbedBuilder()
.setTitle(`${msgconfig.Ticket.AddUserToTicket}`)
.setDescription(`${msgconfig.Ticket.AddUserToTicket}`)
.setColor(supportbot.Embed.Colours.General);
await interaction.reply({
embeds: [AddUserThreadTicket],
ephemeral: true,
});
break;
case "ticketremoveuser":
await removeUserFromThread(interaction);
break;
case "ticketclose": case "ticketclose":
const closeCommand = client.commands.get(cmdconfig.CloseTicket.Command); const closeCommand = client.commands.get(cmdconfig.CloseTicket.Command);
if (closeCommand) { if (closeCommand) {
closeCommand.run(interaction); await closeCommand.run(interaction);
} else { } else {
console.error("[X] Close Command Not Found"); console.error("[X] Close Command Not Found");
} }
break; break;
case "supportvc":
const ticketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketInfo = ticketData.tickets.find(t => t.id === interaction.channel.id);
if (ticketInfo) {
const user = interaction.guild.members.cache.get(ticketInfo.user) || ticketInfo.user;
const claimedBy = interaction.guild.members.cache.get(ticketInfo.claimedBy) || ticketInfo.claimedBy;
const Admin = await interaction.guild.roles.fetch(supportbot.Roles.StaffMember.Admin);
const Staff = await interaction.guild.roles.fetch(supportbot.Roles.StaffMember.Staff);
const VCcategory = interaction.guild.channels.cache.find(
(c) => c.name === supportbot.VoiceTickets.Category || c.id === supportbot.VoiceTickets.Category
);
if (!VCcategory) {
return interaction.reply({
content: "The Support VC category does not exist!",
ephemeral: true,
});
}
let username = user ? user.user.username : "Unknown User";
let SupportVoice = await interaction.guild.channels.create({
name: supportbot.VoiceTickets.Name.replace(/%username%/g, username),
type: Discord.ChannelType.GuildVoice,
parent: VCcategory,
permissionOverwrites: [
{
id: user,
allow: [
Discord.PermissionsBitField.Flags.Connect,
Discord.PermissionsBitField.Flags.Speak,
Discord.PermissionsBitField.Flags.ViewChannel,
],
},
{
id: Staff,
allow: [
Discord.PermissionsBitField.Flags.Connect,
Discord.PermissionsBitField.Flags.Speak,
Discord.PermissionsBitField.Flags.ViewChannel,
],
},
{
id: Admin,
allow: [
Discord.PermissionsBitField.Flags.Connect,
Discord.PermissionsBitField.Flags.Speak,
Discord.PermissionsBitField.Flags.ViewChannel,
],
},
{
id: interaction.guild.id,
deny: [Discord.PermissionsBitField.Flags.ViewChannel],
},
],
});
ticketInfo.voiceChannelId = SupportVoice.id;
fs.writeFileSync("./Data/TicketData.json", JSON.stringify(ticketData, null, 2));
const VCDeleteButton = new Discord.ButtonBuilder()
.setCustomId("deleteSupportVC")
.setLabel(supportbot.Buttons.Voice.TicketDeleteText)
.setEmoji(supportbot.Buttons.General.Delete)
.setStyle(supportbot.Buttons.General.DeleteStyle);
const VCDeleteRow = new Discord.ActionRowBuilder().addComponents(VCDeleteButton);
const vcmadeembed = new Discord.EmbedBuilder()
.setDescription(`> **Support VC Created:** <#${SupportVoice.id}>`)
.setColor(supportbot.Embed.Colours.Success);
await interaction.reply({
embeds: [vcmadeembed],
components: [VCDeleteRow],
});
}
break;
case "archiveticket":
const arcembed = new Discord.EmbedBuilder()
.setDescription(msgconfig.Ticket.TicketArchived)
.setColor(supportbot.Embed.Colours.Success);
const CloseButton = new Discord.ButtonBuilder()
.setCustomId("confirmCloseTicket")
.setLabel(supportbot.Ticket.Close.Confirmation_Button)
.setEmoji(supportbot.Ticket.Close.Confirmation_Emoji)
.setStyle(supportbot.Ticket.Close.Confirmation_Style);
const unarchiveButton = new Discord.ButtonBuilder()
.setCustomId("unarchiveTicket")
.setLabel(supportbot.Buttons.Tickets.Unarchive)
.setEmoji(supportbot.Buttons.Tickets.Unarchive_Emoji)
.setStyle(supportbot.Buttons.Tickets.Unarchive_Style);
const ArchivedOptions = new Discord.ActionRowBuilder()
.addComponents(CloseButton, unarchiveButton);
await interaction.channel.send({
embeds: [arcembed],
components: [ArchivedOptions],
});
if (supportbot.Ticket.TicketType === "threads") {
await interaction.channel.setArchived(true);
}
if (supportbot.Ticket.TicketType === "channels") {
const { getRole, getChannel } = interaction.client;
await interaction.channel.setParent(supportbot.Ticket.TicketArchiveCategory);
const ticketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketInfo = ticketData.tickets.find(t => t.id === interaction.channel.id);
if (ticketInfo) {
const user = interaction.guild.members.cache.get(ticketInfo.user) || ticketInfo.user;
const claimedBy = interaction.guild.members.cache.get(ticketInfo.claimedBy) || ticketInfo.claimedBy;
const Admin = await interaction.guild.roles.fetch(supportbot.Roles.StaffMember.Admin);
const Staff = await interaction.guild.roles.fetch(supportbot.Roles.StaffMember.Staff);
if (user && user.id) {
await interaction.channel.permissionOverwrites.edit(user.id, {
ViewChannel: false,
SendMessages: false,
});
}
if (supportbot.ClaimTickets === true) {
if (claimedBy && claimedBy.id) {
await interaction.channel.permissionOverwrites.edit(claimedBy.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
}
if (supportbot.ClaimTickets === false) {
if (Staff && Staff.id) {
await interaction.channel.permissionOverwrites.edit(claimedBy.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
}
if (Admin && Admin.id) {
await interaction.channel.permissionOverwrites.edit(Admin.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
await interaction.channel.permissionOverwrites.edit(interaction.guild.roles.everyone.id, {
ViewChannel: false,
SendMessages: false,
ReadMessageHistory: false,
});
}
}
break;
case "lockticket":
const lockembed = new Discord.EmbedBuilder()
.setDescription(msgconfig.Ticket.TicketLocked)
.setColor(supportbot.Embed.Colours.Success);
const unlockButton = new Discord.ButtonBuilder()
.setCustomId("unlockTicket")
.setLabel(supportbot.Buttons.Tickets.Unlock)
.setEmoji(supportbot.Buttons.Tickets.Unlock_Emoji)
.setStyle(supportbot.Buttons.Tickets.Unlock_Style);
const LockedOptions = new Discord.ActionRowBuilder()
.addComponents(unlockButton);
await interaction.channel.send({
embeds: [lockembed],
components: [LockedOptions],
ephemeral: false,
});
if (supportbot.Ticket.TicketType === "threads") {
await interaction.channel.setLocked(true);
}
break;
case "unlockTicket":
const unlockEmbed = new Discord.EmbedBuilder()
.setDescription(msgconfig.Ticket.TicketUnlocked)
.setColor(supportbot.Embed.Colours.Success);
await interaction.channel.send({
embeds: [unlockEmbed],
ephemeral: false,
});
if (supportbot.Ticket.TicketType === "threads") {
await interaction.channel.setLocked(false);
}
break;
case "enableinvites": case "enableinvites":
await handleInvites(interaction, true); await handleInvites(interaction, true);
break; break;
@@ -84,37 +366,221 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
case "disableinvites": case "disableinvites":
await handleInvites(interaction, false); await handleInvites(interaction, false);
break; break;
case "renameticket":
const modal = new Discord.ModalBuilder()
.setCustomId("renameTicketModal")
.setTitle("Rename Channel")
.addComponents(
new Discord.ActionRowBuilder()
.addComponents(
new Discord.TextInputBuilder()
.setCustomId("renameTicket")
.setLabel("New Channel Name")
.setStyle(Discord.TextInputStyle.Short)
.setRequired(true)
)
);
await interaction.showModal(modal);
break;
} }
} }
} }
if (interaction.isButton()) { if (interaction.customId === 'deleteSupportVC') {
if (interaction.customId === 'createticket') { const ticketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketInfo = ticketData.tickets.find(t => t.id === interaction.channel.id);
if (ticketInfo && ticketInfo.voiceChannelId) {
try { try {
const cmd = client.commands.get(cmdconfig.OpenTicket.Command); const voiceChannel = await interaction.guild.channels.fetch(ticketInfo.voiceChannelId).catch(() => null);
if (!cmd) return;
cmd.run(interaction); if (voiceChannel) {
await voiceChannel.delete();
const successEmbed = new Discord.EmbedBuilder()
.setDescription('✅ Support voice channel has been successfully deleted!')
.setColor(supportbot.Embed.Colours.Success);
await interaction.reply({
embeds: [successEmbed],
ephemeral: true,
});
ticketInfo.voiceChannelId = null;
fs.writeFileSync("./Data/TicketData.json", JSON.stringify(ticketData, null, 2));
} else {
const errorEmbed = new Discord.EmbedBuilder()
.setDescription('❌ The associated voice channel could not be found.')
.setColor(supportbot.Embed.Colours.Error);
await interaction.reply({
embeds: [errorEmbed],
ephemeral: true,
});
}
} catch (error) { } catch (error) {
console.error("Error executing the ticket command:", error); console.error('Error deleting the Voice Channel:', error);
const errorEmbed = new Discord.EmbedBuilder()
.setDescription('❌ **There was an error trying to delete this voice channel. Please try again later.**')
.setColor(supportbot.Embed.Colours.Error);
await interaction.reply({ await interaction.reply({
content: "An error occurred while creating the ticket.", embeds: [errorEmbed],
ephemeral: true, ephemeral: true,
}); });
} }
} else {
const errorEmbed = new Discord.EmbedBuilder()
.setDescription('⚠️ This button is not linked to a valid voice channel.')
.setColor(supportbot.Embed.Colours.Warn);
await interaction.reply({
embeds: [errorEmbed],
ephemeral: true,
});
}
}
if (interaction.isButton()) {
if (interaction.customId === "unarchiveTicket") {
if (supportbot.Ticket.TicketType === "threads") {
await interaction.channel.setArchived(false);
}
if (supportbot.Ticket.TicketType === "channels") {
const { getRole, getChannel } = interaction.client;
await interaction.channel.setParent(supportbot.Ticket.TicketChannelsCategory);
const ticketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketInfo = ticketData.tickets.find(t => t.id === interaction.channel.id);
if (ticketInfo) {
const user = interaction.guild.members.cache.get(ticketInfo.user) || ticketInfo.user;
const claimedBy = interaction.guild.members.cache.get(ticketInfo.claimedBy) || ticketInfo.claimedBy;
const Admin = await interaction.guild.roles.fetch(supportbot.Roles.StaffMember.Admin);
const Staff = await interaction.guild.roles.fetch(supportbot.Roles.StaffMember.Staff);
if (user && user.id) {
await interaction.channel.permissionOverwrites.edit(user.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
if (supportbot.ClaimTickets === true) {
if (claimedBy && claimedBy.id) {
await interaction.channel.permissionOverwrites.edit(claimedBy.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
}
if (supportbot.ClaimTickets === false) {
if (Staff && Staff.id) {
await interaction.channel.permissionOverwrites.edit(claimedBy.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
}
if (Admin && Admin.id) {
await interaction.channel.permissionOverwrites.edit(Admin.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
await interaction.channel.permissionOverwrites.edit(interaction.guild.roles.everyone.id, {
ViewChannel: false,
SendMessages: false,
ReadMessageHistory: false,
});
}
}
const unrcembed = new Discord.EmbedBuilder()
.setDescription(msgconfig.Ticket.TicketUnarchived)
.setColor(supportbot.Embed.Colours.Success);
await interaction.update({
embeds: [unrcembed],
components: [],
});
}
if (interaction.customId === "createticket") {
if (supportbot.Ticket.TicketReason) {
const modal = new Discord.ModalBuilder()
.setCustomId("ticketReasonModal")
.setTitle("Ticket Reason")
.addComponents(
new Discord.ActionRowBuilder().addComponents(
new Discord.TextInputBuilder()
.setCustomId("reasonInput")
.setLabel("Reason")
.setStyle(Discord.TextInputStyle.Paragraph)
.setRequired(true)
)
);
await interaction.showModal(modal);
} else {
try {
const cmd = client.commands.get(cmdconfig.OpenTicket.Command);
if (!cmd) return;
await cmd.run(interaction);
} catch (error) {
console.error("Error executing the ticket command:", error);
await interaction.reply({
content: "An error occurred while creating the ticket.",
ephemeral: true,
});
}
}
} }
if (interaction.customId === "openticket") { if (interaction.customId === "openticket") {
try { if (supportbot.Ticket.TicketReason) {
const cmd = client.commands.get(cmdconfig.OpenTicket); const modal = new Discord.ModalBuilder()
if (!cmd) return; .setCustomId("ticketReasonModal")
cmd.run(interaction); .setTitle("Ticket Reason")
} catch (error) { .addComponents(
console.error(error); new Discord.ActionRowBuilder().addComponents(
new Discord.TextInputBuilder()
.setCustomId("reasonInput")
.setLabel("Reason")
.setStyle(Discord.TextInputStyle.Paragraph)
.setRequired(true)
)
);
await interaction.showModal(modal);
} else {
try {
const cmd = client.commands.get(cmdconfig.OpenTicket.Command);
if (!cmd) return;
await cmd.run(interaction);
} catch (error) {
console.error(error);
}
} }
} }
if (interaction.customId.startsWith('claimticket-')) { if (interaction.customId.startsWith("claimticket-")) {
const ticketChannelId = interaction.customId.split('-')[1]; const ticketChannelId = interaction.customId.split("-")[1];
const ticketChannel = interaction.guild.channels.cache.get(ticketChannelId); const ticketChannel = interaction.guild.channels.cache.get(ticketChannelId);
if (!ticketChannel) { if (!ticketChannel) {
return interaction.reply({ return interaction.reply({
@@ -144,7 +610,7 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
const NoPermsClaim = new Discord.EmbedBuilder() const NoPermsClaim = new Discord.EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.NoPermsToClaimTitle) .setTitle(msgconfig.Ticket.ClaimTickets.NoPermsToClaimTitle)
.setDescription( .setDescription(
msgconfig.Ticket.ClaimTickets.NoPermsToClaim.replace('%channel%', ticketChannel.name) msgconfig.Ticket.ClaimTickets.NoPermsToClaim.replace("%channel%", ticketChannel.name)
) )
.setColor(supportbot.Embed.Colours.Error); .setColor(supportbot.Embed.Colours.Error);
@@ -155,12 +621,32 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
}); });
} }
await ticketChannel.members.add(interaction.user.id); if (supportbot.Ticket.TicketType === "threads") {
await ticketChannel.members.add(interaction.user.id);
} else if (supportbot.Ticket.TicketType === "channels") {
await ticketChannel.permissionOverwrites.create(interaction.user.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
await ticketChannel.permissionOverwrites.create(Admin, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
await ticketChannel.permissionOverwrites.create(interaction.guild.roles.everyone, {
ViewChannel: false,
});
}
const pingclaimedstaff = await ticketChannel.send(`<@${interaction.user.id}>`); const pingclaimedstaff = await ticketChannel.send(`<@${interaction.user.id}>`);
setTimeout(() => { setTimeout(() => {
pingclaimedstaff.delete(); pingclaimedstaff.delete();
}, 2000) }, 2000);
const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8")); const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketIndex = TicketData.tickets.findIndex((t) => t.id === ticketChannelId); const ticketIndex = TicketData.tickets.findIndex((t) => t.id === ticketChannelId);
@@ -174,13 +660,13 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
const claimedEmbed = new Discord.EmbedBuilder(claimMessage.embeds[0].data) const claimedEmbed = new Discord.EmbedBuilder(claimMessage.embeds[0].data)
.setTitle(msgconfig.Ticket.ClaimTickets.ClaimedTitle) .setTitle(msgconfig.Ticket.ClaimTickets.ClaimedTitle)
.setDescription( .setDescription(
msgconfig.Ticket.ClaimTickets.ClaimMessage_Edit.replace('%user%', interaction.user.id).replace('%channel%', ticketChannel.id) msgconfig.Ticket.ClaimTickets.ClaimMessage_Edit.replace("%user%", interaction.user.id).replace("%channel%", ticketChannel.id)
) )
.setColor(supportbot.Embed.Colours.Success); .setColor(supportbot.Embed.Colours.Success);
const successfullyClaimed = new Discord.EmbedBuilder() const successfullyClaimed = new Discord.EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.ClaimedTitle) .setTitle(msgconfig.Ticket.ClaimTickets.ClaimedTitle)
.setDescription(msgconfig.Ticket.ClaimTickets.Claimed.replace('%channel%', ticketChannel.id)) .setDescription(msgconfig.Ticket.ClaimTickets.Claimed.replace("%channel%", ticketChannel.id))
.setColor(supportbot.Embed.Colours.Success); .setColor(supportbot.Embed.Colours.Success);
await claimMessage.edit({ embeds: [claimedEmbed], components: [] }); await claimMessage.edit({ embeds: [claimedEmbed], components: [] });
@@ -191,36 +677,41 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
} }
// SUGGESTION INTERACTIONS [START] // SUGGESTION INTERACTIONS [START]
if (interaction.customId === 'upvote' || interaction.customId === 'downvote' || interaction.customId === 'removevote') { if (
interaction.customId === "upvote" ||
interaction.customId === "downvote" ||
interaction.customId === "removevote"
) {
const suggestion = suggestions[interaction.message.id]; const suggestion = suggestions[interaction.message.id];
if (!suggestion) return interaction.reply({ content: 'Suggestion not found.', ephemeral: true }); if (!suggestion)
return interaction.reply({ content: "Suggestion not found.", ephemeral: true });
const userId = interaction.user.id; const userId = interaction.user.id;
const hasUpvoted = suggestion.upvotes.includes(userId); const hasUpvoted = suggestion.upvotes.includes(userId);
const hasDownvoted = suggestion.downvotes.includes(userId); const hasDownvoted = suggestion.downvotes.includes(userId);
if (interaction.customId === 'upvote') { if (interaction.customId === "upvote") {
if (hasUpvoted) { if (hasUpvoted) {
suggestion.upvotes = suggestion.upvotes.filter(id => id !== userId); suggestion.upvotes = suggestion.upvotes.filter((id) => id !== userId);
} else { } else {
suggestion.upvotes.push(userId); suggestion.upvotes.push(userId);
if (hasDownvoted) { if (hasDownvoted) {
suggestion.downvotes = suggestion.downvotes.filter(id => id !== userId); suggestion.downvotes = suggestion.downvotes.filter((id) => id !== userId);
} }
} }
} else if (interaction.customId === 'downvote') { } else if (interaction.customId === "downvote") {
if (hasDownvoted) { if (hasDownvoted) {
suggestion.downvotes = suggestion.downvotes.filter(id => id !== userId); suggestion.downvotes = suggestion.downvotes.filter((id) => id !== userId);
} else { } else {
suggestion.downvotes.push(userId); suggestion.downvotes.push(userId);
if (hasUpvoted) { if (hasUpvoted) {
suggestion.upvotes = suggestion.upvotes.filter(id => id !== userId); suggestion.upvotes = suggestion.upvotes.filter((id) => id !== userId);
} }
} }
} else if (interaction.customId === 'removevote') { } else if (interaction.customId === "removevote") {
suggestion.upvotes = suggestion.upvotes.filter(id => id !== userId); suggestion.upvotes = suggestion.upvotes.filter((id) => id !== userId);
suggestion.downvotes = suggestion.downvotes.filter(id => id !== userId); suggestion.downvotes = suggestion.downvotes.filter((id) => id !== userId);
} }
fs.writeFileSync("./Data/SuggestionData.json", JSON.stringify(suggestions, null, 2)); fs.writeFileSync("./Data/SuggestionData.json", JSON.stringify(suggestions, null, 2));
@@ -228,25 +719,36 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
const upvoteCount = suggestion.upvotes.length; const upvoteCount = suggestion.upvotes.length;
const downvoteCount = suggestion.downvotes.length; const downvoteCount = suggestion.downvotes.length;
const updatedEmbed = new Discord.EmbedBuilder(interaction.message.embeds[0].data) const updatedEmbed = new Discord.EmbedBuilder(interaction.message.embeds[0].data).setFields([
.setFields([ { name: "Suggestion", value: suggestion.suggestion, inline: true },
{ name: 'Suggestion', value: suggestion.suggestion, inline: true }, { name: "From", value: `<@${suggestion.author}>` },
{ name: 'From', value: `<@${suggestion.author}>` }, {
{ name: `${supportbot.Suggestions.UpvoteEmoji} ${supportbot.Suggestions.UpvoteTitle}`, value: `${upvoteCount}`, inline: true }, name: `${supportbot.Suggestions.UpvoteEmoji} ${supportbot.Suggestions.UpvoteTitle}`,
{ name: `${supportbot.Suggestions.DownvoteEmoji} ${supportbot.Suggestions.DownvoteTitle}`, value: `${downvoteCount}`, inline: true } value: `${upvoteCount}`,
]); inline: true,
},
{
name: `${supportbot.Suggestions.DownvoteEmoji} ${supportbot.Suggestions.DownvoteTitle}`,
value: `${downvoteCount}`,
inline: true,
},
]);
await interaction.update({ embeds: [updatedEmbed] }); await interaction.update({ embeds: [updatedEmbed] });
} }
} }
async function handleInvites(interaction, enable) { async function handleInvites(interaction, enable) {
if (supportbot.Ticket.Invites.StaffOnly) { if (supportbot.Ticket.Invites.StaffOnly) {
const { getRole, getChannel } = interaction.client;
const SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild); const SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
const Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild); const Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!SupportStaff || !Admin) { if (!SupportStaff || !Admin) {
return interaction.reply("Some roles seem to be missing!\nPlease check for errors when starting the bot."); return interaction.reply(
"Some roles seem to be missing!\nPlease check for errors when starting the bot."
);
} }
const NoPerms = new Discord.EmbedBuilder() const NoPerms = new Discord.EmbedBuilder()
@@ -256,7 +758,10 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
) )
.setColor(supportbot.Embed.Colours.Warn); .setColor(supportbot.Embed.Colours.Warn);
if (!interaction.member.roles.cache.has(SupportStaff.id) && !interaction.member.roles.cache.has(Admin.id)) { if (
!interaction.member.roles.cache.has(SupportStaff.id) &&
!interaction.member.roles.cache.has(Admin.id)
) {
return interaction.reply({ embeds: [NoPerms] }); return interaction.reply({ embeds: [NoPerms] });
} }
} }
@@ -264,9 +769,7 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
await interaction.channel.setInvitable(enable); await interaction.channel.setInvitable(enable);
const message = enable ? msgconfig.Ticket.EnableInvites : msgconfig.Ticket.DisableInvites; const message = enable ? msgconfig.Ticket.EnableInvites : msgconfig.Ticket.DisableInvites;
const embed = new Discord.EmbedBuilder() const embed = new Discord.EmbedBuilder().setDescription(message).setColor(supportbot.Embed.Colours.General);
.setDescription(message)
.setColor(supportbot.Embed.Colours.General);
await interaction.reply({ await interaction.reply({
embeds: [embed], embeds: [embed],
@@ -274,5 +777,55 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
}); });
} }
// Modal submission handler for editing profile
if (interaction.type === Discord.InteractionType.ModalSubmit && interaction.customId === "editProfileModal") {
const userId = interaction.user.id;
const profilePath = `./Data/Profiles/${userId}.json`;
let profileData;
if (fs.existsSync(profilePath)) {
profileData = JSON.parse(fs.readFileSync(profilePath, "utf8"));
} else {
profileData = {
bio: "",
timezone: "",
clockedIn: false,
};
}
try {
const newBio = interaction.fields.getTextInputValue("bio");
const newTimezone = interaction.fields.getTextInputValue("timezone");
profileData.bio = newBio;
profileData.timezone = newTimezone;
fs.writeFileSync(profilePath, JSON.stringify(profileData, null, 2));
const updatedProfileEmbed = new Discord.EmbedBuilder()
.setTitle(`${interaction.user.username}'s Profile`)
.setColor(supportbot.Embed.Colours.General)
.setThumbnail(interaction.user.displayAvatarURL())
.addFields(
{ name: "Bio", value: newBio || "No bio set.", inline: false },
{ name: "Timezone", value: newTimezone || "No timezone set.", inline: true }
);
if (profileData.clockedIn !== undefined) {
updatedProfileEmbed.addFields({
name: "Clocked In Status",
value: profileData.clockedIn ? "✅ Clocked In" : "❌ Clocked Out",
inline: true,
});
}
await interaction.reply({ embeds: [updatedProfileEmbed], ephemeral: true });
} catch (error) {
console.error("Failed to update profile data: ", error);
await interaction.reply({
content: "There was an error while updating your profile. Please try again later.",
ephemeral: true,
});
}
}
}); });
+43
View File
@@ -0,0 +1,43 @@
const { EmbedBuilder } = require('discord.js');
const fs = require("fs");
const yaml = require("js-yaml");
const Event = require("../Structures/Event.js");
// Load the configuration files
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
module.exports = new Event("messageDelete", async (client, message) => {
// Check if the message is from a guild and not from a bot
if (!message.guild || message.author.bot) return;
// Get the delete log channel either by ID or by name
const deleteLogChannel = message.guild.channels.cache.get(supportbot.MessageDelete.Channel) ||
message.guild.channels.cache.find(channel => channel.name === supportbot.MessageDelete.Channel);
// Create the embed for the deleted message
const deletedMessageEmbed = new EmbedBuilder()
.setColor(supportbot.MessageDelete.Colour)
.setTitle("Message Deleted")
.setDescription(`> **Channel:** <#${message.channel.id}>\n> **Message ID** [${message.id}](https://discord.com/channels/${message.guild.id}/${message.channel.id}/${message.id})\n> **Message author:** ${message.author.tag} (${message.author.id})\n> **Message Crated:** <t:${Math.floor(message.createdTimestamp / 1000)}:F>`)
.addFields(
{ name: 'Message', value: message.content || '*No content*', inline: false }
)
.setThumbnail(message.author.displayAvatarURL())
.setTimestamp();
// If the message contains an attachment, add it to the embed
if (message.attachments.size > 0) {
const attachment = message.attachments.first();
deletedMessageEmbed.addFields({ name: 'Attachment', value: `[${attachment.name}](${attachment.proxyURL})` });
}
// Send the embed to the delete log channel if the channel is found
if (deleteLogChannel) {
deleteLogChannel.send({
embeds: [deletedMessageEmbed]
});
}
});
+57
View File
@@ -0,0 +1,57 @@
const { EmbedBuilder } = require('discord.js');
const fs = require("fs");
const yaml = require("js-yaml");
const Event = require("../Structures/Event.js");
// Load the configuration files
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
module.exports = new Event("messageUpdate", async (client, oldMessage, newMessage) => {
// Check if the message is from a guild and not from a bot
if (!newMessage.guild || newMessage.author.bot) return;
// Fetch old message if it's not cached (important for message content)
if (!oldMessage.content && oldMessage.partial) {
try {
oldMessage = await oldMessage.fetch();
} catch (err) {
console.error('Error fetching old message:', err);
return;
}
}
// Fallback if content is missing
const oldMessageContent = oldMessage.content || (oldMessage.attachments.size > 0 ? 'Contains attachment(s)' : '*No content*');
const newMessageContent = newMessage.content || (newMessage.attachments.size > 0 ? 'Contains attachment(s)' : '*No content*');
// Get the update log channel either by ID or by name
const updateLogChannel = newMessage.guild.channels.cache.get(supportbot.MessageUpdate.Channel) ||
newMessage.guild.channels.cache.find(channel => channel.name === supportbot.MessageUpdate.Channel);
// Create the embed for the updated message
const updatedMessageEmbed = new EmbedBuilder()
.setColor(supportbot.MessageUpdate.Colour)
.setTitle("Message Updated")
.setDescription(`> **Channel:** <#${newMessage.channel.id}>\n> **Message ID:** [${newMessage.id}](https://discord.com/channels/${newMessage.guild.id}/${newMessage.channel.id}/${newMessage.id})\n> **Message author:** ${newMessage.author.tag} (${newMessage.author.id})\n> **Message Created:** <t:${Math.floor(oldMessage.createdTimestamp / 1000)}:F>`)
.addFields(
{ name: 'Old Message', value: oldMessageContent, inline: false },
{ name: 'New Message', value: newMessageContent, inline: false }
)
.setThumbnail(newMessage.author.displayAvatarURL())
.setTimestamp();
// If the new message contains an attachment, add it to the embed
if (newMessage.attachments.size > 0) {
const attachment = newMessage.attachments.first();
updatedMessageEmbed.addFields({ name: 'Attachment', value: `[${attachment.name}](${attachment.proxyURL})` });
}
// Send the embed to the update log channel if the channel is found
if (updateLogChannel) {
updateLogChannel.send({
embeds: [updatedMessageEmbed]
});
}
});
+28 -87
View File
@@ -26,7 +26,7 @@ const msgconfig = yaml.load(
const Event = require("../Structures/Event.js"); const Event = require("../Structures/Event.js");
let chan1 = client.channels.cache.get(panelconfig.Channel); let chan1 = client.channels.cache.get(supportbot.Ticket.TicketHome);
module.exports = new Event("ready", async (client, interaction) => { module.exports = new Event("ready", async (client, interaction) => {
const { getRole, getChannel, getCategory } = client; const { getRole, getChannel, getCategory } = client;
@@ -59,7 +59,7 @@ module.exports = new Event("ready", async (client, interaction) => {
}); });
} }
console.log(`\u001b[32m`, `――――――――――――――――――――――――――――――――――――――――――――`); console.log(`\u001b[33m`, `――――――――――――――――――――――――――――――――――――――――――――`);
console.log(` `); console.log(` `);
console.log(`\u001b[31m`, `┏━━━┓╋╋╋╋╋╋╋╋╋╋╋╋╋┏┓┏━━┓╋╋╋┏┓`); console.log(`\u001b[31m`, `┏━━━┓╋╋╋╋╋╋╋╋╋╋╋╋╋┏┓┏━━┓╋╋╋┏┓`);
console.log(`\u001b[31m`, `┃┏━┓┃╋╋╋╋╋╋╋╋╋╋╋╋┏┛┗┫┏┓┃╋╋┏┛┗┓`); console.log(`\u001b[31m`, `┃┏━┓┃╋╋╋╋╋╋╋╋╋╋╋╋┏┛┗┫┏┓┃╋╋┏┛┗┓`);
@@ -70,77 +70,35 @@ module.exports = new Event("ready", async (client, interaction) => {
console.log(`\u001b[31m`, `┗╋╋╋╋╋╋╋┃┃╋┃┃`); console.log(`\u001b[31m`, `┗╋╋╋╋╋╋╋┃┃╋┃┃`);
console.log(`\u001b[31m`, `╋╋╋╋╋╋╋┗┛╋┗┛`); console.log(`\u001b[31m`, `╋╋╋╋╋╋╋┗┛╋┗┛`);
console.log(` `); console.log(` `);
console.log(`\u001b[32m`, `――――――――――――――――――――――――――――――――――――――――――――`); console.log(`\u001b[33m`, `――――――――――――――――――――――――――――――――――――――――――――`);
console.log(` `);
console.log(`\u001b[33m`, `${supportbot.General.Name} | [${supportbot.SupportBot_Version}]`, `\u001b[32m`, `Connected to Discord`,);
console.log("\u001b[32m", "SupportBot created by Emerald Development ");
console.log(` `); console.log(` `);
console.log( console.log(`\u001b[33m`, `――――――――――――――――― [Links] ――――――――――――――――――`);
`\u001b[31m`, console.log("\u001b[32m", "Discord: https://dsc.gg/emerald-dev");
`${supportbot.General.Name} | [${supportbot.SupportBot_Version}]`, console.log("\u001b[32m", "Website: https://emeraldsrv.com");
`\u001b[32m`, console.log("\u001b[32m", "Community: https://community.emeraldsrv.com");
`Connected to Discord`, console.log("\u001b[32m", "Documentation: https://docs.emeraldsrv.com");
);
console.log(`\u001b[31m`, `SupportBot proudly created by Emerald Development`);
console.log(` `); console.log(` `);
console.log(`\u001b[33m`, `――――――――――――――――― [Invite] ――――――――――――――――――`);
console.log( console.log("\u001b[32m", `Invite URL: https://discord.com/api/oauth2/authorize?client_id=${client.user.id}&permissions=8&scope=bot%20applications.commands`);
`\u001b[31m`,
`Discord`,
`\u001b[32m`,
`https://dsc.gg/emerald-dev`,
);
console.log(
`\u001b[31m`,
`Website`,
`\u001b[32m`,
`https://emeraldsrv.com`,
);
console.log(
`\u001b[31m`,
`Marketplace`,
`\u001b[32m`,
`https://market.emeraldsrv.com`,
);
console.log(
`\u001b[31m`,
`Documentation`,
`\u001b[32m`,
`https://emeraldsrv.com/third-party`,
);
console.log(` `); console.log(` `);
console.log(`\u001b[33m`, `――――――――――――――――― [Config Check] ――――――――――――――――――`);
console.log( console.log("\u001b[32m", "Config initialization...");
`\u001b[31m`,
`Invite URL:`,
`\u001b[36m`,
`https://discord.com/api/oauth2/authorize?client_id=` +
client.user.id +
`&permissions=8&scope=bot%20applications.commands`
);
console.log(` `);
console.log(`\u001b[32m`, `――――――――――――――――――――――――――――――――――――――――――――`);
const roles = [ const roles = [
supportbot.Roles.StaffMember.Admin, supportbot.Roles.StaffMember.Admin,
supportbot.Roles.StaffMember.Staff, supportbot.Roles.StaffMember.Staff
supportbot.Roles.ModRoles.Blacklisted,
supportbot.Roles.ModRoles.Muted
]; ];
supportbot.Departments.forEach((department) => roles.push(department.role));
if (supportbot.Roles.AutoRole.Role) roles.push(supportbot.Roles.AutoRole.Role); if (supportbot.Roles.AutoRole.Role) roles.push(supportbot.Roles.AutoRole.Role);
const channels = [ const channels = [
supportbot.Suggestions.Channel, supportbot.Suggestions.Channel,
supportbot.Ticket.Log.TicketLog, supportbot.Ticket.Log.TicketLog,
supportbot.Ticket.Log.TranscriptLog, supportbot.Ticket.Log.TranscriptLog,
panelconfig.Channel, supportbot.Ticket.TicketHome,
supportbot.Welcome.Channel, supportbot.Welcome.Channel,
supportbot.Leave.Channel, supportbot.Leave.Channel,
supportbot.Translate.Log supportbot.Translate.Log
@@ -168,35 +126,18 @@ module.exports = new Event("ready", async (client, interaction) => {
// if (!find) missingCat.push(cat); // if (!find) missingCat.push(cat);
// } // }
if (missingR.length > 0) { const missingRoles = await Promise.all(roles.map(role => getRole(role, client.guilds.cache.first())));
console.log( const missingChannels = await Promise.all(channels.map(channel => getChannel(channel, client.guilds.cache.first())));
"\u001b[31m",
`[MISSING CONFIGURATION]`, if (missingRoles.some(role => !role)) {
"\u001b[33m", console.log("\u001b[31m", `Missing roles in your server configuration: ${missingRoles.filter(role => !role).join(', ')}`);
`The following Roles could not be found in your server.\n`, `\u001b[31m`, `[${missingR.join(
", "
)}]`, `\n `,
);
} }
if (missingC.length > 0) { if (missingChannels.some(channel => !channel)) {
console.log( console.log("\u001b[31m", `Missing channels in your server configuration: ${missingChannels.filter(channel => !channel).join(', ')}`);
"\u001b[31m",
`[MISSING CONFIGURATION]`,
"\u001b[33m",
`The following Channels could not be found in your server.\n`, `\u001b[31m`, `[${missingC.join(
", "
)}]`, `\n `
);
}
if (missingCat.length > 0) {
console.log(
"\u001b[31m",
`[MISSING CONFIGURATION]`,
"\u001b[33m",
`The following Categories could not be found in your server.\n`, `\u001b[31m`, `[${missingCat.join(
", "
)}]`, `\n `,
);
} }
console.log("\u001b[32m", "Configs initialized, No problems were detected.");
console.log(` `);
}); });
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2022 Emerald Services Copyright (c) 2024 Emerald Services
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+10 -2
View File
@@ -13,11 +13,11 @@
</a> </a>
<a href="https://github.com/discordjs"> <a href="https://github.com/discordjs">
<img src="https://img.shields.io/badge/discord.js-v13.6.x-blue.svg?style=for-the-badge&logo=javascript.svg" alt="shield.png"> <img src="https://img.shields.io/badge/DISCORD.JS%20-%20v14-blue?style=for-the-badge" alt="shield.png">
</a> </a>
<a href="https://github.com/nodejs/node"> <a href="https://github.com/nodejs/node">
<img src="https://img.shields.io/badge/node.js-v16.14.x-green.svg?style=for-the-badge&logo=node" alt="shield.png"> <img src="https://img.shields.io/badge/NODE.JS%20-%20v20.x-green?style=for-the-badge" alt="shield.png">
</a> </a>
<a href="https://github.com/Emerald-Services/SupportBot/releases"> <a href="https://github.com/Emerald-Services/SupportBot/releases">
@@ -84,5 +84,13 @@ Released under the [MIT](https://opensource.org/licenses/MIT) license.
<img src="https://contrib.rocks/image?repo=Emerald-Services/SupportBot"> <img src="https://contrib.rocks/image?repo=Emerald-Services/SupportBot">
</a> </a>
## Hosting
You can use our [Pterodactyl egg](https://github.com/Emerald-Services/Pterodactyl-Egg) to host it on your own panel. We currently don't offer hosting for you, altough we might add this in the future.
If you don't know how to install pterodactyl, you could always host it on replit:
[![Run on Repl.it](https://repl.it/badge/github/ItsNyoty/IKI-bot)]([https://repl.it/github/ItsNyoty/iki](https://repl.it/github/Emerald-Services/SupportBot
)
+7 -1
View File
@@ -1,6 +1,6 @@
// SupportBot | Emerald Services // SupportBot | Emerald Services
// Addon Structure // Addon Structure
class Command { class Command {
constructor(options) { constructor(options) {
this.name = options.name; this.name = options.name;
@@ -10,7 +10,13 @@ class Command {
this.run = options.run; this.run = options.run;
this.defaultPermission = true; this.defaultPermission = true;
} }
// Method to dynamically update options
setOptions(options) {
this.options = options;
}
} }
class Event { class Event {
constructor(event, run) { constructor(event, run) {
this.event = event; this.event = event;
+47 -27
View File
@@ -1,12 +1,8 @@
// C:\Users\ollie\Documents\GitHub\SupportBot\Structures\Client.js
// SupportBot | Emerald Services
// Client Structure
const fs = require("fs"); const fs = require("fs");
const Discord = require("discord.js"); const Discord = require("discord.js");
const { GatewayIntentBits, Partials } = require('discord.js'); const { GatewayIntentBits, Partials } = require('discord.js');
const yaml = require("js-yaml"); const yaml = require("js-yaml");
const { Command, Event } = require('./Addon.js'); // Adjust the path as needed
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8")); const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8")); const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
@@ -17,9 +13,10 @@ class Client extends Discord.Client {
intents: [ intents: [
GatewayIntentBits.Guilds, GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessages GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
], ],
partials: [Partials.Channel] partials: [Partials.Message, Partials.Channel]
}); });
this.commands = new Discord.Collection(); this.commands = new Discord.Collection();
@@ -89,39 +86,56 @@ class Client extends Discord.Client {
tempCommandFiles = tempCommandFiles.filter(item => item !== "Ping.js"); tempCommandFiles = tempCommandFiles.filter(item => item !== "Ping.js");
} }
if (cmdconfig.Ping.Enabled === false) { if (cmdconfig.AddUser.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "Ping.js"); tempCommandFiles = tempCommandFiles.filter(item => item !== "addUser.js");
} }
if (supportbot.Ticket.ClaimTickets.Enabled === false) { if (cmdconfig.RemoveUser.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "ticketstats.js"); tempCommandFiles = tempCommandFiles.filter(item => item !== "removeUser.js");
} }
if (cmdconfig.ForceAddUser.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "forceaddUser.js");
}
if (cmdconfig.Profile.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "profile.js");
}
if (cmdconfig.Settings.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "settings.js");
}
if (cmdconfig.Mod.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "mod.js");
}
const commandFiles = tempCommandFiles; const commandFiles = tempCommandFiles;
const commands = commandFiles.map((file) => require(`../Commands/${file}`)); const commands = commandFiles.map((file) => require(`../Commands/${file}`));
// Commands // Load main commands
console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Commands ▬▬▬▬▬▬▬"); console.log(`\u001b[33m`, "――――――――――――――――――――――――――――――――――――――――――――");
commands.forEach((cmd) => { commands.forEach((cmd) => {
console.log(`\u001b[32m`, `[CMD]`, `\u001b[37;1m`, `${cmd.name}`, `\u001b[32;1m`, "Loaded"); console.log(`\u001b[32m`, `[CMD]`, `\u001b[37;1m`, `${cmd.name}`, `\u001b[32;1m`, "Loaded");
this.commands.set(cmd.name, cmd); this.commands.set(cmd.name, cmd);
}); });
console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Commands ▬▬▬▬▬▬▬"); // Load addon commands and events
console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Commands ▬▬▬▬▬▬▬");
if (supportbot.General.Addons.Enabled) { if (supportbot.General.Addons.Enabled) {
const addonFiles = fs.readdirSync("./Addons").filter((file) => file.endsWith(".js")); const addonFiles = fs.readdirSync("./Addons").filter((file) => file.endsWith(".js"));
const addons = addonFiles.map((file) => { const addons = addonFiles.map((file) => {
let addon = require(`../Addons/${file}`); const addon = require(`../Addons/${file}`);
addon.name = file.split(".")[0]; addon.name = file.split(".")[0];
return addon; return addon;
}); });
// Addons // Addons
console.log(" "); console.log(" ");
console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Addons ▬▬▬▬▬▬▬"); console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Addons ▬▬▬▬▬▬▬");
addons.forEach((addon) => { addons.forEach((addon) => {
console.log( console.log(
@@ -132,18 +146,24 @@ class Client extends Discord.Client {
`\u001b[32;1m`, `\u001b[32;1m`,
"Loaded" "Loaded"
); );
addon.commands?.forEach((command) => {
this.commands.set(command.name, command); if (addon instanceof Command) {
}); this.commands.set(addon.name, addon);
addon.events?.forEach((event) => { }
this.on(event.event, (...args) => event.run(this, ...args));
}); if (addon.events && Array.isArray(addon.events)) {
addon.events.forEach((event) => {
if (event instanceof Event) {
this.on(event.event, (...args) => event.run(this, ...args));
}
});
}
}); });
console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Addons ▬▬▬▬▬▬▬"); console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Addons ▬▬▬▬▬▬▬");
} }
// Slash Commands // Register Slash Commands
this.once("ready", async () => { this.once("ready", async () => {
await this.guilds.cache.first()?.commands.set(this.commands); await this.guilds.cache.first()?.commands.set(this.commands);
@@ -156,7 +176,7 @@ class Client extends Discord.Client {
}); });
console.log(" "); console.log(" ");
console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Events ▬▬▬▬▬▬▬"); console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Events ▬▬▬▬▬▬▬");
fs.readdirSync("./Events").filter((file) => file.endsWith(".js")).forEach((file) => { fs.readdirSync("./Events").filter((file) => file.endsWith(".js")).forEach((file) => {
const event = require(`../Events/${file}`); const event = require(`../Events/${file}`);
@@ -164,7 +184,7 @@ class Client extends Discord.Client {
this.on(event.event, (...args) => event.run(this, ...args)); this.on(event.event, (...args) => event.run(this, ...args));
}); });
console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Events ▬▬▬▬▬▬▬"); console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Events ▬▬▬▬▬▬▬");
if (process.argv[2] !== "test") { if (process.argv[2] !== "test") {
await this.login(token); await this.login(token);
} else { } else {
+1 -1
View File
@@ -19,7 +19,7 @@ const supportbot = yaml.load(
const Client = require("./Structures/Client.js"); const Client = require("./Structures/Client.js");
const client = new Client({ const client = new Client({
intents: ['Guilds', 'GuildMembers', 'GuildMessages'] intents: ['Guilds', 'GuildMembers', 'GuildMessages', 'MessageContent']
}); });
client.start(supportbot.General.Token); client.start(supportbot.General.Token);
+5 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "supportbot", "name": "supportbot",
"version": "8.0.1", "version": "8.3",
"description": "SupportBot is an open-sourced support system for discord. It's completely free to use with high amounts of customization to match your own brand.", "description": "SupportBot is an open-sourced support system for discord. It's completely free to use with high amounts of customization to match your own brand.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@@ -16,14 +16,16 @@
"bugs": { "bugs": {
"url": "https://github.com/Emerald-Services/SupportBot/issues" "url": "https://github.com/Emerald-Services/SupportBot/issues"
}, },
"homepage": "https://github.com/Emerald-Services/SupportBot#readme", "homepage": "https://community.emeraldsrv.dev",
"dependencies": { "dependencies": {
"@paypal/checkout-server-sdk": "^1.0.3",
"@vitalets/google-translate-api": "^8.0.0", "@vitalets/google-translate-api": "^8.0.0",
"axios": "^1.7.2", "axios": "^1.7.2",
"chatgpt": "^5.2.5",
"discord.js": "^14.13.0", "discord.js": "^14.13.0",
"js": "^0.1.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"moment": "^2.29.3", "moment": "^2.29.3",
"ms": "^2.1.3",
"openai": "^4.50.0" "openai": "^4.50.0"
} }
} }