(channel, closer, reason = 'No reason provided')
| 234 | } |
| 235 | |
| 236 | export async function closeTicket(channel, closer, reason = 'No reason provided') { |
| 237 | try { |
| 238 | const ticketData = await getTicketData(channel.guild.id, channel.id); |
| 239 | if (!ticketData) { |
| 240 | return { success: false, error: 'This is not a ticket channel' }; |
| 241 | } |
| 242 | |
| 243 | const config = await getGuildConfig(channel.client, channel.guild.id); |
| 244 | const dmOnClose = config.dmOnClose !== false; |
| 245 | const closedCategoryId = config.ticketClosedCategoryId || null; |
| 246 | let movedToClosedCategory = false; |
| 247 | |
| 248 | ticketData.status = 'closed'; |
| 249 | ticketData.closedBy = closer.id; |
| 250 | ticketData.closedAt = new Date().toISOString(); |
| 251 | ticketData.closeReason = reason; |
| 252 | |
| 253 | await saveTicketData(channel.guild.id, channel.id, ticketData); |
| 254 | |
| 255 | if (closedCategoryId && channel.parentId !== closedCategoryId) { |
| 256 | const closedCategory = channel.guild.channels.cache.get(closedCategoryId) |
| 257 | || await channel.guild.channels.fetch(closedCategoryId).catch(() => null); |
| 258 | |
| 259 | if (closedCategory?.type === ChannelType.GuildCategory) { |
| 260 | try { |
| 261 | await channel.setParent(closedCategoryId, { lockPermissions: false }); |
| 262 | movedToClosedCategory = true; |
| 263 | } catch (moveError) { |
| 264 | logger.warn(`Could not move ticket ${channel.id} to closed category ${closedCategoryId}: ${moveError.message}`); |
| 265 | } |
| 266 | } else { |
| 267 | logger.warn(`Configured closed category is invalid for guild ${channel.guild.id}: ${closedCategoryId}`); |
| 268 | } |
| 269 | } |
| 270 | |
| 271 | if (dmOnClose) { |
| 272 | try { |
| 273 | const ticketCreator = await channel.client.users.fetch(ticketData.userId).catch(() => null); |
| 274 | if (ticketCreator) { |
| 275 | const dmEmbed = createEmbed({ |
| 276 | title: '🎫 Your Ticket Has Been Closed', |
| 277 | description: `Your ticket **${channel.name}** has been closed.\n\n**Reason:** ${reason}\n**Closed by:** ${closer.tag}\n**Closed at:** <t:${Math.floor(Date.now() / 1000)}:F>\n\nThank you for using our support system! If you have any further questions, feel free to create a new ticket.`, |
| 278 | color: '#e74c3c', |
| 279 | footer: { text: `Ticket ID: ${ticketData.id}` } |
| 280 | }); |
| 281 | |
| 282 | await ticketCreator.send({ embeds: [dmEmbed] }); |
| 283 | |
| 284 | try { |
| 285 | const feedbackEmbed = createEmbed({ |
| 286 | title: '⭐ How was your support experience?', |
| 287 | description: `We'd love to know how we did with **${channel.name}**.\nSelect a rating below — it only takes a second!`, |
| 288 | color: '#F1C40F', |
| 289 | footer: { text: 'Your feedback helps us improve.' }, |
| 290 | }); |
| 291 | |
| 292 | const base = `ticket_feedback:${channel.guild.id}:${channel.id}`; |
| 293 | const starsRow = new ActionRowBuilder().addComponents( |
no test coverage detected