const { EmbedBuilder, PermissionsBitField } = require('discord.js'); const axios = require('axios'); const path = require('path'); const fs = require('fs'); const SocksProxyAgent = require('socks-proxy-agent'); const archiver = require('archiver'); const { Storage } = require('megajs'); module.exports = { name: 'ticketclose', description: 'Close an existing support ticket and generate an HTML transcript.', async execute(interaction) { await interaction.deferReply({ephemeral: true}); const channelName = interaction.channel.name; const ticketCreatorId = interaction.channel.topic; const ticketCreator = interaction.guild.members.cache.get(ticketCreatorId); if (!channelName.startsWith('🧾⏐ticket-') && !channelName.startsWith('🛒⏐ticket-') && !channelName.startsWith('📝⏐ticket-') && !channelName.startsWith('💾⏐ticket-')) { return interaction.editReply('This command can only be used in ticket channels.'); } const sortedMessages = await fetchAllMessages(interaction.channel); sortedMessages.sort((a, b) => a.createdTimestamp - b.createdTimestamp); await interaction.followUp({content: 'Generating the HTML transcript...', ephemeral: true}); const transcriptsFolderPath = path.join(__dirname, '..', 'transcripts'); const imagesFolderPath = path.join(transcriptsFolderPath, 'images'); if (!fs.existsSync(transcriptsFolderPath)) { fs.mkdirSync(transcriptsFolderPath, {recursive: true}); } if (!fs.existsSync(imagesFolderPath)) { fs.mkdirSync(imagesFolderPath); } let htmlContent = ``; htmlContent += `
Transcript for ${interaction.channel.name}
`; for (const message of sortedMessages) { const formattedDate = `${message.createdAt.toLocaleDateString()} ${message.createdAt.toLocaleTimeString()}`; htmlContent += `
${formattedDate} ${message.author.username}: ${message.content}
`; // Handling embeds in the message message.embeds.forEach(embed => { htmlContent += "
"; if (embed.title) { htmlContent += `
${embed.title}
`; } if (embed.description) { htmlContent += `
${embed.description}
`; } if (embed.fields) { embed.fields.forEach(field => { htmlContent += `
${field.name}: ${field.value}
`; }); } if (embed.image) { htmlContent += `
`; } if (embed.thumbnail) { htmlContent += `
`; } if (embed.footer) { htmlContent += ``; } htmlContent += "
"; // Closing embed div }); // Handling attachments (images and videos) if (message.attachments.size > 0) { await Promise.all(message.attachments.map(async (attachment) => { // Add a delay before processing each attachment await delay(3000); // Adjust the 1000 milliseconds delay as needed // Remove query parameters from the URL and extract the file extension const attachmentUrl = attachment.url.split('?')[0]; const fileExtension = attachmentUrl.split('.').pop(); const attachmentFilename = path.join(imagesFolderPath, `${attachment.id}.${fileExtension}`); if (/\.(jpeg|jpg|gif|png)$/.test(attachmentUrl)) { // Handle image files const imageBuffer = await fetchImage(attachmentUrl); fs.writeFileSync(attachmentFilename, imageBuffer); htmlContent += `
`; } else if (/\.(mp4|webm|ogg)$/.test(attachmentUrl)) { // Handle video files const videoBuffer = await fetchImage(attachmentUrl); fs.writeFileSync(attachmentFilename, videoBuffer); htmlContent += `
`; } })); } } htmlContent += ``; const sanitizedChannelName = channelName.replace(/[^a-z0-9]/gi, '_').toLowerCase(); const filename = `transcript_${sanitizedChannelName}.html`; const filepath = path.join(transcriptsFolderPath, filename); fs.writeFileSync(filepath, htmlContent); const zipFilePath = path.join(transcriptsFolderPath, `transcript_${sanitizedChannelName}.zip`); const output = fs.createWriteStream(zipFilePath); const archive = archiver('zip', { zlib: { level: 9 }}); archive.pipe(output); archive.file(filepath, { name: filename }); archive.directory(imagesFolderPath, 'images'); archive.finalize(); output.on('close', async () => { const fileSize = fs.statSync(zipFilePath).size; const fileSizeLimit = 8 * 1024 * 1024; // 8 MB for standard Discord users if (fileSize > fileSizeLimit) { console.log(`ZIP file is too large to upload: ${zipFilePath}`); try { const megaLink = await uploadToMega(zipFilePath); const transcriptEmbed = createTranscriptEmbed(interaction, ticketCreatorId, megaLink); // Use createTranscriptEmbed function const transcriptsChannelId = 'removed'; const transcriptsChannel = interaction.guild.channels.cache.get(transcriptsChannelId); await transcriptsChannel.send({ embeds: [transcriptEmbed] }); console.log('Transcript uploaded to Mega and link sent in Discord.'); } catch (error) { console.error('Error uploading to Mega:', error); await interaction.followUp({ content: 'There was an error uploading the transcript to Mega.', ephemeral: true }); } } else { // If the file is within size limit, send the file along with the embed const transcriptEmbed = createTranscriptEmbed(interaction, ticketCreatorId,null); // No Mega link needed const transcriptsChannelId = 'removed'; const transcriptsChannel = interaction.guild.channels.cache.get(transcriptsChannelId); await transcriptsChannel.send({ embeds: [transcriptEmbed], files: [zipFilePath] }); console.log('Transcript uploaded to Discord.'); } if (ticketCreator) { const dmEmbed = createDmEmbed(interaction); ticketCreator.send({ embeds: [dmEmbed] }).catch(error => console.error(`Could not send DM to ${ticketCreator.user.tag}`, error)); } // Close the ticket interaction.channel.delete(); // Clean up images folder fs.rmSync(imagesFolderPath, { recursive: true, force: true }); // The ZIP file remains saved on the server console.log(`Transcript saved locally: ${zipFilePath}`); }).on('error', (err) => { console.error(`Error creating ZIP file: ${err}`); }); } }; async function fetchAllMessages(channel) { let allMessages = []; let lastID; while (true) { const options = { limit: 100 }; if (lastID) options.before = lastID; const fetchedMessages = await channel.messages.fetch(options); if (fetchedMessages.size === 0) break; allMessages = allMessages.concat([...fetchedMessages.values()]); lastID = fetchedMessages.last().id; } return allMessages; } async function fetchImage(url) { try { console.log(`Attempting to fetch image from URL: ${url}`); const response = await axios.get(url, { responseType: 'arraybuffer', headers: { 'User-Agent': 'RemovedforPrivacy/1.0', 'Authorization': `Bot ${botToken}` //try bot token for auth, no work. } }); console.log(`Image fetched successfully: ${url}`); return Buffer.from(response.data, 'binary'); } catch (error) { console.error(`Error fetching image from URL: ${url}`, error); //Retry once after a delay if it's a 5xx server error if (error.response && error.response.status >= 500) { console.log(`Retrying image fetch for URL: ${url} after delay...`); await delay(1000); // Wait for 1 second before retrying try { const retryResponse = await axios.get(url, { responseType: 'arraybuffer', headers: { 'Authorization': `Bot ${botToken}` //did not work. } }); console.log(`Image fetched successfully after retry: ${url}`); return Buffer.from(retryResponse.data, 'binary'); } catch (retryError) { console.error(`Retry failed for image URL: ${url}`, retryError); return null; } } else { return null; } } } function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function uploadToMega(filePath) { try { console.log(`Starting Mega upload for file: ${filePath}`); const storage = await new Storage({ email: '', password: '' }).ready; const fileData = fs.readFileSync(filePath); const file = await storage.upload(path.basename(filePath), fileData).complete; // Attempt to obtain the link correctly const megaLink = typeof file.link === 'function' ? file.link() : file.link; console.log('File uploaded to Mega successfully! Link:', megaLink); return megaLink; } catch (error) { console.error('Error during Mega upload:', error); throw error; } } const currentDate = new Date(); const unixTimestamp = Math.floor(currentDate.getTime() / 1000); // Convert to seconds const timestampString = ``; function createTranscriptEmbed(interaction, ticketCreatorId, megaLink) { // Format the timestamp const ticketCreator = interaction.guild.members.cache.get(ticketCreatorId); const ticketCreatorName = ticketCreator ? ticketCreator.user.username : 'Unknown User'; return new EmbedBuilder() .setColor(0x0099FF) .setTitle('Ticket Transcript') .setDescription(`Details of the closed ticket. [Download Transcript](${megaLink})`) .setAuthor({ name: interaction.guild.name, iconURL: interaction.guild.iconURL(), url: 'https://discord.com/' // Replace with a relevant URL if needed }) .setThumbnail(interaction.user.displayAvatarURL()) .addFields( { name: 'Opened By', value: ticketCreatorName }, { name: 'Closed By', value: interaction.user.username }, { name: 'Closed At', value: timestampString } ) .setTimestamp(); } function createDmEmbed(interaction) { return new EmbedBuilder() .setColor('#ffa400') .setTitle('Your Ticket Has Been Closed') .setDescription(`Your ticket in ${interaction.guild.name} has been successfully closed.`) .addFields({ name: 'Ticket', value: interaction.channel.name }) .setTimestamp(); }