From d9cb72f22ef18ae556ec3107dfc39d24ee078957 Mon Sep 17 00:00:00 2001 From: 1tzEmerald Date: Sat, 9 Nov 2024 02:44:48 +0000 Subject: [PATCH 1/3] 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 5a1c4ad99ce740a5eaa30f4019b2055b768b5514 Mon Sep 17 00:00:00 2001 From: 1tzEmerald Date: Sat, 9 Nov 2024 02:46:25 +0000 Subject: [PATCH 2/3] 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 0b8b092cb91f6e9c79ea117400431640651db1f6 Mon Sep 17 00:00:00 2001 From: 1tzEmerald Date: Sat, 9 Nov 2024 13:03:55 +0000 Subject: [PATCH 3/3] 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;