From dffa5d4161f3074d2d6175a0bfff26858e6bfacf Mon Sep 17 00:00:00 2001 From: 1tzEmerald Date: Sat, 9 Nov 2024 02:32:06 +0000 Subject: [PATCH 01/10] Update close.js --- Commands/close.js | 215 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 149 insertions(+), 66 deletions(-) diff --git a/Commands/close.js b/Commands/close.js index 7e87d22..e880d9b 100644 --- a/Commands/close.js +++ b/Commands/close.js @@ -17,7 +17,7 @@ module.exports = new Command({ name: "reason", description: "Ticket Close Reason", type: Discord.ApplicationCommandOptionType.String, - }, + } ], permissions: cmdconfig.CloseTicket.Permission, @@ -35,33 +35,33 @@ module.exports = new Command({ 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); + .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 isThread = interaction.channel.type === Discord.ChannelType.PrivateThread; if ( - (supportbot.Ticket.TicketType === "threads" && interaction.channel.type !== Discord.ChannelType.PrivateThread) || + (supportbot.Ticket.TicketType === "threads" && !isThread) || (supportbot.Ticket.TicketType === "channels" && interaction.channel.type !== Discord.ChannelType.GuildText) ) { const NotTicketChannel = new Discord.EmbedBuilder() .setTitle("Invalid Channel!") .setDescription(`This command can only be used in a ${supportbot.Ticket.TicketType === "threads" ? "ticket thread" : "ticket channel"}.`) - .setColor(supportbot.Embed.Colours.Warn); // Ensure valid color + .setColor(supportbot.Embed.Colours.Warn); return interaction.reply({ embeds: [NotTicketChannel], ephemeral: true }); } - await interaction.deferReply(); // Deferring to avoid double replies + await interaction.deferReply(); - // Reading TicketData.json once for efficiency let tickets; try { tickets = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8")); } catch (err) { - console.error('Error reading ticket data file:', err); + console.error("Error reading ticket data file:", err); return interaction.followUp({ content: "There was an error loading ticket data." }); } @@ -72,15 +72,16 @@ module.exports = new Command({ const Exists = new Discord.EmbedBuilder() .setTitle("No Ticket Found!") .setDescription(msgconfig.Error.NoValidTicket) - .setColor(supportbot.Embed.Colours.Warn); // Ensure valid color + .setColor(supportbot.Embed.Colours.Warn); return interaction.followUp({ embeds: [Exists] }); } let reason = interaction.options?.getString("reason") || "No Reason Provided."; + const ticketUserId = ticket.user; if (supportbot.Ticket.ReviewSystem.Enabled) { - const rating = await collectReviewRating(interaction); - const comment = await collectReviewComment(interaction); + const rating = await collectReviewRating(interaction, ticketUserId); + const comment = await collectReviewComment(interaction, ticketUserId); const reviewChannel = await getChannel(supportbot.Ticket.ReviewSystem.Channel, interaction.guild); const reviewer = supportbot.Ticket.ClaimTickets ? `<@${ticket.claimedBy}>` : null; @@ -103,11 +104,11 @@ module.exports = new Command({ if (supportbot.Ticket.ClaimTickets.Enabled) { reviewEmbed.setDescription( - `**${msgconfig.ReviewSystem.ReviewEmbed.ReviewedStaffTitle}** ${reviewer}\n**${msgconfig.ReviewSystem.ReviewEmbed.ReviewedByTitle}** <@${interaction.user.id}>` || `N/A` + `**${msgconfig.ReviewSystem.ReviewEmbed.ReviewedStaffTitle}** ${reviewer}\n**${msgconfig.ReviewSystem.ReviewEmbed.ReviewedByTitle}** <@${ticketUserId}>` || `N/A` ); } else { reviewEmbed.setDescription( - `**${msgconfig.ReviewSystem.ReviewEmbed.ReviewedByTitle}** <@${interaction.user.id}>` + `**${msgconfig.ReviewSystem.ReviewEmbed.ReviewedByTitle}** <@${ticketUserId}>` ); } @@ -126,11 +127,11 @@ module.exports = new Command({ }, }); -async function collectReviewRating(interaction) { +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); // Ensure valid color + .setDescription(`${msgconfig.ReviewSystem.Rate.Description}`) + .setColor(supportbot.Embed.Colours.General); const stars = new Discord.ActionRowBuilder().addComponents( new Discord.StringSelectMenuBuilder() @@ -146,6 +147,7 @@ async function collectReviewRating(interaction) { ); await interaction.followUp({ + content: `<@${ticketUserId}>`, embeds: [reviewPrompt], components: [stars], ephemeral: true, @@ -153,27 +155,27 @@ async function collectReviewRating(interaction) { const starRating = await interaction.channel.awaitMessageComponent({ componentType: Discord.ComponentType.StringSelect, - filter: (i) => i.customId === "starRating" && i.user.id === interaction.user.id, - time: 30000, + filter: (i) => i.customId === "starRating" && i.user.id === ticketUserId, }); - await starRating.deferUpdate(); return starRating.values[0]; } -async function collectReviewComment(interaction) { +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); // Ensure valid color + .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 === interaction.user.id; - const commentCollection = await interaction.channel.awaitMessages({ filter, max: 1, time: 30000 }); + 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"; } @@ -181,12 +183,21 @@ async function collectReviewComment(interaction) { async function handleCloseTicket(interaction, reason, ticket, TicketData) { const { getChannel } = interaction.client; - 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:", err); + return interaction.followUp("Failed to load ticket data."); + } + let tUser = interaction.client.users.cache.get(ticket.user); let transcriptChannel = await getChannel(supportbot.Ticket.Log.TicketDataLog, interaction.guild); - if (!transcriptChannel) - return console.log("Ticket Log Missing"); + if (!transcriptChannel) { + console.log("Transcript channel missing or inaccessible"); + return interaction.followUp("Error: Transcript log channel is missing or bot lacks permission."); + } try { tickets.tickets[TicketData].open = false; @@ -194,57 +205,129 @@ async function handleCloseTicket(interaction, reason, ticket, TicketData) { const transcriptEmbed = new Discord.EmbedBuilder() .setTitle(msgconfig.TicketLog.Title) - .setColor(msgconfig.TicketLog.Colour) // Ensure valid color + .setColor(msgconfig.TicketLog.Colour) .setFooter({ text: supportbot.Embed.Footer, iconURL: interaction.user.displayAvatarURL() }) - .setDescription(`> **Ticket:** ${interaction.channel.name} (\`${interaction.channel.id}\`)\n> **User:** ${tUser?.username || "N/A"}#${tUser?.discriminator || "N/A"} (\`${tUser?.id || ticket.user}\`)\n> **Closed by:** <@${interaction.user.id}>`) + .setDescription( + `> **Ticket:** ${interaction.channel.name} (\`${interaction.channel.id}\`)\n` + + `> **User:** ${tUser?.tag || "Unknown User"} (\`${tUser?.id || ticket.user}\`)\n` + + `> **Closed by:** <@${interaction.user.id}>` + ) .addFields({ name: "Reason", value: `\`\`\`${reason}\`\`\``, inline: true }); let msgs = await interaction.channel.messages.fetch({ limit: 100 }); - let html = createTranscriptHTML(interaction, msgs); + let html = createTranscriptHTML(ticket, msgs); - const fileName = `${interaction.channel.name}.html`; - let file = new Discord.AttachmentBuilder(Buffer.from(html), { name: `SPOILER_${fileName}` }); + const fileName = `${interaction.channel.id}-transcript.html`; + fs.writeFileSync(`./Data/Transcripts/${fileName}`, html); - transcriptChannel.send({ + await transcriptChannel.send({ embeds: [transcriptEmbed], - files: [file], + files: [`./Data/Transcripts/${fileName}`], + spoiler: true, }); + + if (interaction.channel.type === Discord.ChannelType.GuildText) { + await interaction.channel.delete(); + } else if (interaction.channel.type === Discord.ChannelType.PrivateThread) { + await interaction.channel.setLocked(true); + await interaction.channel.setArchived(true); + } } catch (err) { - console.log("Error closing ticket:", err); + console.error("Error handling ticket close:", err); + interaction.followUp("An error occurred while closing the ticket."); } - - await interaction.channel.delete(); } -function createTranscriptHTML(interaction, msgs) { - let html = ` - - - - - -

Ticket Transcript

-

Ticket: ${interaction.channel.name}

-

User: ${interaction.user.tag}

-
+function createTranscriptHTML(ticket, reason) { + const messages = ticket.messages || []; + return ` + + + + + + +
+

Ticket Transcript

+

Server Name: Emerald Services

+

Ticket: ${ticket.id}

+

Messages: ${messages.length} Messages

+
+ ${messages.map(msg => ` +
+ User Avatar +
+

${msg.username}

+

${msg.content}

+

${msg.timestamp}

+
+
`).join('')} +
+ Download Transcript +
+ + `; - - msgs.reverse().forEach((msg) => { - html += ` -
- ${msg.author.tag}: ${msg.content} -
- `; - }); - - html += ` -
- - - `; - return html; } + + From 1bb343e49a2eb104ba433152821c2817db45a192 Mon Sep 17 00:00:00 2001 From: 1tzEmerald Date: Sat, 9 Nov 2024 02:32:28 +0000 Subject: [PATCH 02/10] Update ticket.js --- Commands/ticket.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/Commands/ticket.js b/Commands/ticket.js index 5260bd9..ed84267 100644 --- a/Commands/ticket.js +++ b/Commands/ticket.js @@ -27,7 +27,7 @@ async function isBlacklisted(userId) { blacklistData = JSON.parse(fs.readFileSync("./Data/BlacklistedUsers.json", "utf8")); } catch (error) { console.error("Error reading BlacklistedUsers.json:", error); - return false; // Assume not blacklisted if there's an error + return false; } if (!blacklistData || !Array.isArray(blacklistData.blacklistedUsers)) { @@ -312,20 +312,6 @@ module.exports = new Command({ }); } - if (User.roles.cache.has(supportbot.Roles.ModRoles.Blacklisted)) { - return interaction.reply({ - content: msgconfig.Ticket.Blacklisted, - ephemeral: true, - }); - } - - if (User.roles.cache.has(supportbot.Roles.ModRoles.Muted)) { - return interaction.reply({ - content: msgconfig.Ticket.Muted, - ephemeral: true, - }); - } - let ticketNumberID = await TicketNumberID.pad(); const TicketSubject = TicketReason || msgconfig.Ticket.InvalidSubject; From 357a85fa3d5a6c8e305e60f4bd92b21023083379 Mon Sep 17 00:00:00 2001 From: 1tzEmerald Date: Sat, 9 Nov 2024 02:37:14 +0000 Subject: [PATCH 03/10] Update close.js --- Commands/close.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Commands/close.js b/Commands/close.js index e880d9b..3922671 100644 --- a/Commands/close.js +++ b/Commands/close.js @@ -229,8 +229,7 @@ async function handleCloseTicket(interaction, reason, ticket, TicketData) { if (interaction.channel.type === Discord.ChannelType.GuildText) { await interaction.channel.delete(); } else if (interaction.channel.type === Discord.ChannelType.PrivateThread) { - await interaction.channel.setLocked(true); - await interaction.channel.setArchived(true); + await interaction.channel.delete(); } } catch (err) { console.error("Error handling ticket close:", err); From 58eb37124e2c891fe8dda26d2f24a55cc8b68d87 Mon Sep 17 00:00:00 2001 From: 1tzEmerald Date: Sat, 9 Nov 2024 02:44:39 +0000 Subject: [PATCH 04/10] Update close.js --- Commands/close.js | 191 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 133 insertions(+), 58 deletions(-) diff --git a/Commands/close.js b/Commands/close.js index 3922671..a412414 100644 --- a/Commands/close.js +++ b/Commands/close.js @@ -179,7 +179,6 @@ async function collectReviewComment(interaction, ticketUserId) { const comment = commentCollection.first()?.content; return comment && comment.toLowerCase() !== "no" ? comment : "No Comment Provided"; } - async function handleCloseTicket(interaction, reason, ticket, TicketData) { const { getChannel } = interaction.client; @@ -200,7 +199,49 @@ async function handleCloseTicket(interaction, reason, ticket, TicketData) { } try { + // Fetch all messages from the channel + let allMessages = []; + let lastId = null; + + while (true) { + 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() @@ -210,25 +251,34 @@ async function handleCloseTicket(interaction, reason, ticket, TicketData) { .setDescription( `> **Ticket:** ${interaction.channel.name} (\`${interaction.channel.id}\`)\n` + `> **User:** ${tUser?.tag || "Unknown User"} (\`${tUser?.id || ticket.user}\`)\n` + - `> **Closed by:** <@${interaction.user.id}>` + `> **Closed by:** <@${interaction.user.id}>\n` + + `> **Message Count:** ${transcriptData.length}` ) .addFields({ name: "Reason", value: `\`\`\`${reason}\`\`\``, inline: true }); - let msgs = await interaction.channel.messages.fetch({ limit: 100 }); - let html = createTranscriptHTML(ticket, msgs); + const html = createTranscriptHTML( + { + id: interaction.channel.id, + messages: transcriptData, + name: interaction.channel.name + }, + reason + ); const fileName = `${interaction.channel.id}-transcript.html`; fs.writeFileSync(`./Data/Transcripts/${fileName}`, html); await transcriptChannel.send({ embeds: [transcriptEmbed], - files: [`./Data/Transcripts/${fileName}`], - spoiler: true, + files: [{ + attachment: `./Data/Transcripts/${fileName}`, + name: fileName, + spoiler: true + }] }); - if (interaction.channel.type === Discord.ChannelType.GuildText) { - await interaction.channel.delete(); - } else if (interaction.channel.type === Discord.ChannelType.PrivateThread) { + if (interaction.channel.type === Discord.ChannelType.GuildText || + interaction.channel.type === Discord.ChannelType.PrivateThread) { await interaction.channel.delete(); } } catch (err) { @@ -238,10 +288,12 @@ async function handleCloseTicket(interaction, reason, ticket, TicketData) { } function createTranscriptHTML(ticket, reason) { - const messages = ticket.messages || []; return ` + + +
-

Ticket Transcript

-

Server Name: Emerald Services

-

Ticket: ${ticket.id}

-

Messages: ${messages.length} Messages

-
- ${messages.map(msg => ` -
- User Avatar -
-

${msg.username}

-

${msg.content}

-

${msg.timestamp}

-
-
`).join('')} +
+

Ticket Transcript

+

Channel: ${ticket.name}

+

Ticket ID: ${ticket.id}

+

Message Count: ${ticket.messages.length}

+

Close Reason: ${reason}

+
+ +
+ ${ticket.messages.map(msg => ` +
+
+ Avatar + ${msg.username} + ${new Date(msg.timestamp).toLocaleString()} +
+
+ ${msg.content} + + ${msg.embeds.map(embed => ` +
+ ${embed.title ? `
${embed.title}
` : ''} + ${embed.description ? `
${embed.description}
` : ''} + ${embed.fields.map(field => ` +
+ ${field.name}: +
${field.value}
+
+ `).join('')} +
+ `).join('')} + + ${msg.attachments.map(att => ` + + 📎 ${att.name} + + `).join('')} +
+
+ `).join('')}
- Download Transcript
`; } - - From fcf441a190ed78666538ec388b59532ab97f366c Mon Sep 17 00:00:00 2001 From: 1tzEmerald Date: Sat, 9 Nov 2024 02:46:35 +0000 Subject: [PATCH 05/10] Update close.js --- Commands/close.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Commands/close.js b/Commands/close.js index a412414..340484c 100644 --- a/Commands/close.js +++ b/Commands/close.js @@ -218,10 +218,8 @@ async function handleCloseTicket(interaction, reason, ticket, TicketData) { 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, @@ -239,7 +237,6 @@ async function handleCloseTicket(interaction, reason, ticket, TicketData) { })) })); - // Update ticket status tickets.tickets[TicketData].open = false; tickets.tickets[TicketData].messages = transcriptData; fs.writeFileSync("./Data/TicketData.json", JSON.stringify(tickets, null, 4)); @@ -272,7 +269,7 @@ async function handleCloseTicket(interaction, reason, ticket, TicketData) { embeds: [transcriptEmbed], files: [{ attachment: `./Data/Transcripts/${fileName}`, - name: fileName, + name: `SPOILER_${fileName}`, spoiler: true }] }); From 3cdfaef94235562ebfc7eef1366c7ec87f6669a2 Mon Sep 17 00:00:00 2001 From: 1tzEmerald Date: Sat, 9 Nov 2024 13:03:41 +0000 Subject: [PATCH 06/10] Update profile.js --- Commands/profile.js | 245 +++++++++++++++++++++++++++----------------- 1 file changed, 153 insertions(+), 92 deletions(-) diff --git a/Commands/profile.js b/Commands/profile.js index a25f448..2368525 100644 --- a/Commands/profile.js +++ b/Commands/profile.js @@ -20,35 +20,6 @@ function updateClockedInStatus(profileEmbed, clockedIn) { return profileEmbed; } -function formatSchedule(schedule) { - if (!schedule || Object.keys(schedule).length === 0) return 'No schedule set'; - - const days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']; - return days - .filter(day => schedule[day]) - .map(day => `${day.charAt(0).toUpperCase() + day.slice(1)}: ${schedule[day].start} - ${schedule[day].end}`) - .join('\n'); -} - -function calculateWeeklyHours(schedule) { - if (!schedule) return 0; - - let totalMinutes = 0; - Object.values(schedule).forEach(day => { - if (day && day.start && day.end) { - const [startHour, startMin] = day.start.split(':').map(Number); - const [endHour, endMin] = day.end.split(':').map(Number); - - const startMinutes = startHour * 60 + startMin; - const endMinutes = endHour * 60 + endMin; - - totalMinutes += endMinutes - startMinutes; - } - }); - - return Math.round(totalMinutes / 60 * 10) / 10; // Round to 1 decimal place -} - module.exports = new Command({ name: cmdconfig.Profile.Command, description: cmdconfig.Profile.Description, @@ -64,7 +35,7 @@ module.exports = new Command({ ], async run(interaction) { - await interaction.deferReply({ ephemeral: true }); + await interaction.deferReply({ ephemeral: true }); const userOption = interaction.options.getUser('user'); const viewingUser = userOption || interaction.user; @@ -72,7 +43,7 @@ module.exports = new Command({ const interactionUserId = interaction.user.id; const user = interaction.guild.members.cache.get(viewingUserId); - const { getRole } = interaction.client; + 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); @@ -96,14 +67,12 @@ module.exports = new Command({ profileData = { bio: "", timezone: "", - clockedIn: false, - schedule: {} + clockedIn: false }; fs.writeFileSync(profilePath, JSON.stringify(profileData, null, 2)); } - const { bio, timezone, clockedIn, schedule } = profileData; - const weeklyHours = calculateWeeklyHours(schedule); + const { bio, timezone, clockedIn } = profileData; let profileEmbed = new EmbedBuilder() .setTitle(`${viewingUser.username}'s Profile`) @@ -115,11 +84,7 @@ module.exports = new Command({ ); if (isStaff) { - profileEmbed.addFields( - { name: 'Clocked In Status', value: clockedIn ? '✅ Clocked In' : '❌ Clocked Out', inline: true }, - { name: 'Weekly Hours', value: `${weeklyHours} hours`, inline: true }, // Now inline with Schedule - { name: 'Schedule', value: formatSchedule(schedule), inline: true } - ); + profileEmbed.addFields({ name: 'Clocked In Status', value: clockedIn ? '✅ Clocked In' : '❌ Clocked Out', inline: true }); } const buttonRow = new ActionRowBuilder(); @@ -136,10 +101,6 @@ module.exports = new Command({ new ButtonBuilder() .setCustomId('clockInOut') .setLabel(clockedIn ? 'Clock Out' : 'Clock In') - .setStyle(ButtonStyle.Secondary), - new ButtonBuilder() - .setCustomId('editSchedule') - .setLabel('Edit Schedule') .setStyle(ButtonStyle.Secondary) ); } @@ -157,7 +118,7 @@ module.exports = new Command({ await interaction.editReply({ embeds: [profileEmbed], components: buttonRow.components.length > 0 ? [buttonRow] : [] }); const filter = i => ( - ['editProfile', 'clockInOut', 'viewTicketStats', 'editSchedule'].includes(i.customId) + ['editProfile', 'clockInOut', 'viewTicketStats', 'showOpenTickets', 'backToStats', 'backToProfile'].includes(i.customId) && i.user.id === interactionUserId ); @@ -174,7 +135,8 @@ module.exports = new Command({ .setCustomId('bio') .setLabel('Bio') .setStyle(TextInputStyle.Paragraph) - .setValue(profileData.bio || '') + .setPlaceholder('Enter your bio') + .setValue(String(bio)) .setRequired(false) ), new ActionRowBuilder().addComponents( @@ -182,7 +144,8 @@ module.exports = new Command({ .setCustomId('timezone') .setLabel('Timezone') .setStyle(TextInputStyle.Short) - .setValue(profileData.timezone || '') + .setPlaceholder('Enter your timezone') + .setValue(String(timezone)) .setRequired(false) ) ); @@ -193,6 +156,7 @@ module.exports = new Command({ 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); @@ -200,60 +164,157 @@ module.exports = new Command({ clockedInUsers.delete(viewingUserId); } - profileEmbed = updateClockedInStatus(profileEmbed, profileData.clockedIn); - const updatedRow = new ActionRowBuilder().addComponents( - ...buttonRow.components.map(button => { - if (button.data.custom_id === 'clockInOut') { - return new ButtonBuilder() - .setCustomId('clockInOut') - .setLabel(profileData.clockedIn ? 'Clock Out' : 'Clock In') - .setStyle(ButtonStyle.Secondary); - } - return ButtonBuilder.from(button.data); - }) - ); + const updatedButtonRow = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId('editProfile') + .setLabel('Edit Profile') + .setStyle(ButtonStyle.Secondary) + ); - await i.update({ embeds: [profileEmbed], components: [updatedRow] }); + 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') { - // Handle ticket stats viewing here - const ticketStatsEmbed = new EmbedBuilder() - .setTitle('Ticket Stats') - .setDescription('Here are your ticket stats...') - .setColor(supportbot.Embed.Colours.General); + 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; - await i.update({ embeds: [ticketStatsEmbed], components: [] }); - } + let totalResponseTime = 0; + let responseCount = 0; - if (i.customId === 'editSchedule') { - const modal = new ModalBuilder() - .setCustomId('editScheduleModal') - .setTitle('Edit Schedule'); - - ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'].forEach(day => { - modal.addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId(`${day.toLowerCase()}Start`) - .setLabel(`${day} Start Time (HH:mm)`) - .setStyle(TextInputStyle.Short) - .setRequired(false) - .setValue(schedule[day.toLowerCase()]?.start || '') - ), - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId(`${day.toLowerCase()}End`) - .setLabel(`${day} End Time (HH:mm)`) - .setStyle(TextInputStyle.Short) - .setRequired(false) - .setValue(schedule[day.toLowerCase()]?.end || '') - ) - ); + ticketsClaimed.forEach(ticket => { + if (ticket.claimedAt && ticket.createdAt) { + const responseTime = new Date(ticket.claimedAt) - new Date(ticket.createdAt); + totalResponseTime += responseTime; + responseCount++; + } }); - await i.showModal(modal); + 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; From d468ab157561e24ab5a13919165d679919b3c8e5 Mon Sep 17 00:00:00 2001 From: 1tzEmerald Date: Sun, 10 Nov 2024 13:18:50 +0000 Subject: [PATCH 07/10] Delete Data/Profiles directory --- Data/Profiles/000000000000000.json | 29 ----------------------------- Data/Profiles/0000000000000000.json | 5 ----- 2 files changed, 34 deletions(-) delete mode 100644 Data/Profiles/000000000000000.json delete mode 100644 Data/Profiles/0000000000000000.json diff --git a/Data/Profiles/000000000000000.json b/Data/Profiles/000000000000000.json deleted file mode 100644 index 2e52c6b..0000000 --- a/Data/Profiles/000000000000000.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "bio": "", - "timezone": "", - "clockedIn": true, - "workingHours": 0, - "lastClockInTime": null, - "schedule": { - "monday": { - "start": "09:00", - "end": "17:00" - }, - "tuesday": { - "start": "09:00", - "end": "17:00" - }, - "wednesday": { - "start": "09:00", - "end": "17:00" - }, - "thursday": { - "start": "09:00", - "end": "17:00" - }, - "friday": { - "start": "09:00", - "end": "17:00" - } - } -} \ No newline at end of file diff --git a/Data/Profiles/0000000000000000.json b/Data/Profiles/0000000000000000.json deleted file mode 100644 index 0088126..0000000 --- a/Data/Profiles/0000000000000000.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "bio": "", - "timezone": "", - "clockedIn": false -} From 71e33dbf66250bf260b1717b1a40af238d98a0f8 Mon Sep 17 00:00:00 2001 From: 1tzEmerald Date: Sun, 10 Nov 2024 13:30:27 +0000 Subject: [PATCH 08/10] Update close.js --- Commands/close.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Commands/close.js b/Commands/close.js index 340484c..ef853cf 100644 --- a/Commands/close.js +++ b/Commands/close.js @@ -218,8 +218,10 @@ async function handleCloseTicket(interaction, reason, ticket, TicketData) { 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, @@ -237,6 +239,7 @@ async function handleCloseTicket(interaction, reason, ticket, TicketData) { })) })); + // Update ticket status tickets.tickets[TicketData].open = false; tickets.tickets[TicketData].messages = transcriptData; fs.writeFileSync("./Data/TicketData.json", JSON.stringify(tickets, null, 4)); @@ -284,6 +287,25 @@ async function handleCloseTicket(interaction, reason, ticket, TicketData) { } } +function parseMarkdown(content) { + // Convert **bold** text to tags + content = content.replace(/\*\*(.*?)\*\*/g, '$1'); + + // Convert _italic_ text to tags + content = content.replace(/_(.*?)_/g, '$1'); + + // Convert inline `code` to tags + content = content.replace(/`(.*?)`/g, '$1'); + + // Convert [text](link) to tags + content = content.replace(/\[(.*?)\]\((.*?)\)/g, '$1'); + + // Convert multiline code blocks ```code``` to
 tags
+  content = content.replace(/```(.*?)```/gs, '
$1
'); + + return content; +} + function createTranscriptHTML(ticket, reason) { return ` @@ -373,7 +395,7 @@ function createTranscriptHTML(ticket, reason) { ${new Date(msg.timestamp).toLocaleString()}
- ${msg.content} + ${parseMarkdown(msg.content)} ${msg.embeds.map(embed => `
From 4b2d53d3f4000236feb41ae28585ad949bfecd47 Mon Sep 17 00:00:00 2001 From: chorisancio3d Date: Mon, 3 Feb 2025 21:43:03 -0300 Subject: [PATCH 09/10] Added "InvalidChannel" message (required by suggest.js) Fixed a double quote that was preventing the bot to start correctly. --- Configs/messages.yml | 6 +----- Configs/supportbot.yml | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Configs/messages.yml b/Configs/messages.yml index 9733627..3daca83 100644 --- a/Configs/messages.yml +++ b/Configs/messages.yml @@ -268,8 +268,4 @@ Error: 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." - - - - - + InvalidChannel: ":x: **Error!** The suggestions channel has not been set up correctly." \ No newline at end of file diff --git a/Configs/supportbot.yml b/Configs/supportbot.yml index ea6b514..2587027 100644 --- a/Configs/supportbot.yml +++ b/Configs/supportbot.yml @@ -278,7 +278,7 @@ SelectMenus: # ------------------------- MessageDelete: - Channel: "MESSAGE_DELETE_CHANNEL_ID"" + Channel: "MESSAGE_DELETE_CHANNEL_ID" Colour: "#d92525" MessageUpdate: From d69fb0e26d9c869e145f7f1b642a18df38fd37ea Mon Sep 17 00:00:00 2001 From: Nyo <58821897+ItsNyoty@users.noreply.github.com> Date: Thu, 22 May 2025 20:30:06 +0200 Subject: [PATCH 10/10] Update supportbot.yml --- Configs/supportbot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configs/supportbot.yml b/Configs/supportbot.yml index ea6b514..2587027 100644 --- a/Configs/supportbot.yml +++ b/Configs/supportbot.yml @@ -278,7 +278,7 @@ SelectMenus: # ------------------------- MessageDelete: - Channel: "MESSAGE_DELETE_CHANNEL_ID"" + Channel: "MESSAGE_DELETE_CHANNEL_ID" Colour: "#d92525" MessageUpdate: