2021-10-28 18:03:27 -05:00
const fs = require ( "fs" );
const Discord = require ( "discord.js" );
const yaml = require ( "js-yaml" );
2022-08-12 01:35:36 +01:00
2024-10-10 19:14:45 +01:00
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" ));
2021-10-28 18:03:27 -05:00
const Command = require ( "../Structures/Command.js" );
module . exports = new Command ({
2022-07-23 07:50:12 +01:00
name : cmdconfig . CloseTicket . Command ,
description : cmdconfig . CloseTicket . Description ,
2022-07-22 13:19:05 +01:00
type : Discord . ApplicationCommandType . ChatInput ,
2021-12-18 18:57:26 -06:00
options : [
2021-10-28 18:03:27 -05:00
{
name : "reason" ,
description : "Ticket Close Reason" ,
2022-07-22 13:19:05 +01:00
type : Discord . ApplicationCommandOptionType . String ,
2021-10-28 18:03:27 -05:00
},
],
2022-07-23 07:50:12 +01:00
permissions : cmdconfig . CloseTicket . Permission ,
2021-10-28 18:03:27 -05:00
async run ( interaction ) {
2022-01-30 00:07:04 -06:00
const { getRole , getChannel } = interaction . client ;
2024-06-12 12:54:31 +01:00
2022-08-12 01:35:36 +01:00
if ( supportbot . Ticket . Close . StaffOnly ) {
let SupportStaff = await getRole ( supportbot . Roles . StaffMember . Staff , interaction . guild );
let Admin = await getRole ( supportbot . Roles . StaffMember . Admin , interaction . guild );
2024-06-12 12:54:31 +01:00
2021-12-18 19:39:59 -06:00
if ( ! SupportStaff || ! Admin )
2024-10-10 19:14:45 +01:00
return interaction . reply ( "Some roles seem to be missing!\nPlease check for errors when starting the bot." );
2022-08-12 01:35:36 +01:00
2024-06-12 12:54:31 +01:00
const NoPerms = new Discord . EmbedBuilder ()
. setTitle ( "Invalid Permissions!" )
2024-10-10 19:14:45 +01:00
. setDescription ( ` ${ msgconfig . Error . IncorrectPerms } \n\nRole Required: \` ${ supportbot . Roles . StaffMember . Staff } \` or \` ${ supportbot . Roles . StaffMember . Admin } \`` )
2024-06-12 12:54:31 +01:00
. setColor ( supportbot . Embed . Colours . Warn );
2021-12-18 19:39:59 -06:00
2024-10-10 19:14:45 +01:00
if ( ! interaction . member . roles . cache . has ( SupportStaff . id ) && ! interaction . member . roles . cache . has ( Admin . id ))
2021-12-18 19:39:59 -06:00
return interaction . reply ({ embeds : [ NoPerms ] });
}
2024-06-12 12:54:31 +01:00
2024-10-10 19:14:45 +01:00
// Check if it's a thread or channel based on config
if (
( supportbot . Ticket . TicketType === "threads" && interaction . channel . type !== Discord . ChannelType . PrivateThread ) ||
( supportbot . Ticket . TicketType === "channels" && interaction . channel . type !== Discord . ChannelType . GuildText )
) {
2024-06-12 12:54:31 +01:00
const NotTicketChannel = new Discord . EmbedBuilder ()
. setTitle ( "Invalid Channel!" )
2024-10-10 19:14:45 +01:00
. setDescription ( `This command can only be used in a ${ supportbot . Ticket . TicketType === "threads" ? "ticket thread" : "ticket channel" } .` )
2024-06-12 12:54:31 +01:00
. setColor ( supportbot . Embed . Colours . Warn );
return interaction . reply ({ embeds : [ NotTicketChannel ], ephemeral : true });
}
2021-12-04 20:57:23 -06:00
await interaction . deferReply ();
2024-06-12 12:54:31 +01:00
let tickets = JSON . parse ( fs . readFileSync ( "./Data/TicketData.json" , "utf8" ));
let TicketData = tickets . tickets . findIndex (( t ) => t . id === interaction . channel . id );
2021-12-18 22:14:11 -06:00
let ticket = tickets . tickets [ TicketData ];
2024-06-12 12:54:31 +01:00
2022-01-29 23:57:10 -06:00
if ( TicketData === - 1 ) {
2022-07-22 13:19:05 +01:00
const Exists = new Discord . EmbedBuilder ()
2021-10-28 18:03:27 -05:00
. setTitle ( "No Ticket Found!" )
2022-08-12 01:35:36 +01:00
. setDescription ( msgconfig . Error . NoValidTicket )
. setColor ( supportbot . Embed . Colours . Warn );
2021-12-04 20:57:23 -06:00
return interaction . followUp ({ embeds : [ Exists ] });
2021-10-28 18:03:27 -05:00
}
2024-06-12 12:54:31 +01:00
let reason = interaction . options ? . getString ( "reason" ) || "No Reason Provided." ;
const CloseButton = new Discord . ButtonBuilder ()
. setCustomId ( "confirmCloseTicket" )
. setLabel ( supportbot . Ticket . Close . Confirmation_Button )
2024-07-30 17:45:35 +01:00
. setEmoji ( supportbot . Ticket . Close . Confirmation_Emoji )
. setStyle ( supportbot . Ticket . Close . Confirmation_Style );
2024-06-12 12:54:31 +01:00
const row = new Discord . ActionRowBuilder (). addComponents ( CloseButton );
const CloseTicketRequest = new Discord . EmbedBuilder ()
. setTitle ( `** ${ supportbot . Ticket . Close . Title } **` )
2024-10-10 19:14:45 +01:00
. setDescription ( ` ${ msgconfig . Ticket . ConfirmClose } ` )
2024-06-12 12:54:31 +01:00
. setColor ( supportbot . Embed . Colours . General );
await interaction . followUp ({
embeds : [ CloseTicketRequest ],
components : [ row ],
});
const filter = ( btnInteraction ) => btnInteraction . customId === "confirmCloseTicket" && btnInteraction . user . id === interaction . user . id ;
interaction . channel . awaitMessageComponent ({ filter , time : 20000 })
. then ( async ( btnInteraction ) => {
await handleCloseTicket ( interaction , reason , ticket , TicketData );
await btnInteraction . reply ({ content : "Ticket successfully closed." , ephemeral : true });
})
. catch (( err ) => {
interaction . followUp ( ` ${ msgconfig . Ticket . CloseTimeout } ` );
console . error ( err );
2021-11-23 15:49:37 -06:00
});
2024-06-12 12:54:31 +01:00
},
});
async function handleCloseTicket ( interaction , reason , ticket , TicketData ) {
const { getChannel } = interaction . client ;
let tickets = JSON . parse ( fs . readFileSync ( "./Data/TicketData.json" , "utf8" ));
let tUser = interaction . client . users . cache . get ( ticket . user );
2024-10-10 19:14:45 +01:00
let transcriptChannel = await getChannel ( supportbot . Ticket . Log . TicketDataLog , interaction . guild );
2024-06-12 12:54:31 +01:00
2024-10-10 19:14:45 +01:00
if ( ! transcriptChannel )
2024-06-12 12:54:31 +01:00
return interaction . followUp ( "Some Channels seem to be missing!" );
try {
tickets . tickets [ TicketData ]. open = false ;
fs . writeFileSync (
"./Data/TicketData.json" ,
JSON . stringify ( tickets , null , 4 ),
( err ) => {
if ( err ) console . error ( err );
2022-06-18 11:19:12 +09:30
}
2024-06-12 12:54:31 +01:00
);
const transcriptEmbed = new Discord . EmbedBuilder ()
2024-10-10 19:14:45 +01:00
. setTitle ( msgconfig . TicketLog . Title )
. setColor ( msgconfig . TicketLog . Colour )
2024-06-12 12:54:31 +01:00
. setFooter ({
text : supportbot . Embed . Footer ,
iconURL : interaction . user . displayAvatarURL (),
})
. 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>
` ;
let file = new Discord . AttachmentBuilder (
Buffer . from ( html ),
{ name : fileName }
);
await transcriptChannel
. send ({ embeds : [ transcriptEmbed ], files : [ file ] })
. catch (( err ) => {
console . error ( err );
});
if ( supportbot . Ticket . DMTranscripts ) {
await tUser
2022-02-17 19:57:11 -06:00
? . send ({ embeds : [ transcriptEmbed ], files : [ file ] })
2024-06-12 12:54:31 +01:00
. catch (( err ) => {
2021-11-23 18:09:06 -06:00
console . error ( err );
2021-10-28 18:03:27 -05:00
});
2024-06-12 12:54:31 +01:00
2021-12-18 22:14:11 -06:00
if ( ticket . subUsers ) {
ticket . subUsers . forEach ( async ( subUser ) => {
2022-01-29 23:57:10 -06:00
await interaction . client . users . cache
2022-01-30 00:07:04 -06:00
. get ( subUser )
2022-02-17 19:57:11 -06:00
? . send ({ embeds : [ transcriptEmbed ], files : [ file ] });
2021-12-15 00:19:40 -06:00
});
}
2024-06-12 12:54:31 +01:00
}
2022-02-27 04:30:15 +00:00
2024-06-12 12:54:31 +01:00
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 );
2022-02-27 04:30:15 +00:00
}
2024-06-12 12:54:31 +01:00
});
} catch ( error ) {
console . error ( error );
}
}
2022-02-27 04:30:15 +00:00
2024-06-12 12:54:31 +01:00
function convertMarkdownToHTML ( text ) {
// Convert **bold** to <strong>bold</strong>
text = text . replace ( /\*\*(.*?)\*\*/g , '<strong>$1</strong>' );
// Convert *italic* to <em>italic</em>
text = text . replace ( /\*(.*?)\*/g , '<em>$1</em>' );
// Convert __underline__ to <u>underline</u>
text = text . replace ( /__(.*?)__/g , '<u>$1</u>' );
// Convert ~~strikethrough~~ to <s>strikethrough</s>
text = text . replace ( /~~(.*?)~~/g , '<s>$1</s>' );
// Convert inline code `code` to <code>code</code>
text = text . replace ( /`(.*?)`/g , '<code>$1</code>' );
// Convert blockquotes > quote to <blockquote>quote</blockquote>
text = text . replace ( /^> (.*?$)/gm , '<blockquote>$1</blockquote>' );
// Convert multiline code blocks ```code``` to <pre><code>code</code></pre>
text = text . replace ( /```(.*?)```/gs, '<pre><code>$1</code></pre>');
// Convert line breaks to <br>
text = text . replace ( /\n/g , '<br>' );
return text ;
}