Tickets: Transcript beim Schließen + Thread-Löschung
- Schließen-Button erstellt jetzt ein Text-Transcript (chronologisch, inkl. Attachment-URLs, max 500 Nachrichten) und schickt es dem Ersteller per DM - Kopie ins Mod-Log (Aufbewahrung fürs Team, mit Hinweis falls DM blockiert) - Thread wird danach gelöscht statt nur archiviert (Fallback: sperren+archivieren, falls dem Bot Lösch-Rechte fehlen) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+4
-10
@@ -9,11 +9,12 @@ import { registerLevels } from './levels.js';
|
||||
import { registerPresence } from './presence.js';
|
||||
import { registerExtras } from './extras.js';
|
||||
import { handleApplyButton, handleApplyModal, handleReviewButton } from './app-forms.js';
|
||||
import { closeTicketWithTranscript } from './tickets.js';
|
||||
import { handleRoleMenuButton } from './role-menus.js';
|
||||
import {
|
||||
addPlaytester, removePlaytester, isWish, bumpWish,
|
||||
getGiveaway, toggleGiveawayEntry, giveawayEntries,
|
||||
saveTicket, getTicket, closeTicket, openTicketOf,
|
||||
saveTicket, openTicketOf,
|
||||
} from '../db.js';
|
||||
import { ChannelType, ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from 'discord.js';
|
||||
import * as ping from './commands/ping.js';
|
||||
@@ -242,15 +243,8 @@ export async function startBot() {
|
||||
}
|
||||
if (interaction.isButton() && interaction.customId === 'ticket_close') {
|
||||
try {
|
||||
const ticket = getTicket(interaction.channelId);
|
||||
if (!ticket) {
|
||||
await interaction.reply({ content: '❌ Das ist kein Ticket-Thread.', flags: MessageFlags.Ephemeral });
|
||||
return;
|
||||
}
|
||||
closeTicket(interaction.channelId);
|
||||
await interaction.reply(`🔒 Ticket geschlossen von **${interaction.member?.displayName ?? interaction.user.username}**.`);
|
||||
await interaction.channel.setLocked(true).catch(() => {});
|
||||
await interaction.channel.setArchived(true).catch(() => {});
|
||||
// Transcript per DM + Mod-Log sichern, dann Thread löschen
|
||||
await closeTicketWithTranscript(interaction);
|
||||
} catch (error) {
|
||||
console.error('[ticket] Schließen fehlgeschlagen:', error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// Ticket-Schließen: Transcript sichern (DM an den Ersteller + Kopie ins Mod-Log),
|
||||
// danach wird der Thread gelöscht statt nur archiviert.
|
||||
import { AttachmentBuilder, EmbedBuilder, MessageFlags } from 'discord.js';
|
||||
import { getTicket, closeTicket } from '../db.js';
|
||||
import { modlogChannelId, brandColor, brandFooter } from '../runtime-settings.js';
|
||||
|
||||
const MAX_MESSAGES = 500;
|
||||
|
||||
/** Alle Thread-Nachrichten chronologisch als Text-Transcript */
|
||||
async function fetchTranscript(channel) {
|
||||
const all = [];
|
||||
let before;
|
||||
while (all.length < MAX_MESSAGES) {
|
||||
const batch = await channel.messages.fetch({ limit: 100, before });
|
||||
if (batch.size === 0) break;
|
||||
all.push(...batch.values());
|
||||
before = batch.last().id;
|
||||
}
|
||||
all.sort((a, b) => a.createdTimestamp - b.createdTimestamp);
|
||||
return all
|
||||
.map((m) => {
|
||||
const ts = new Date(m.createdTimestamp).toISOString().slice(0, 16).replace('T', ' ');
|
||||
const parts = [
|
||||
m.content,
|
||||
[...(m.attachments?.values?.() ?? [])].map((a) => a.url).join(' '),
|
||||
m.embeds?.length ? '[Embed]' : '',
|
||||
].filter(Boolean).join(' ');
|
||||
return `[${ts}] ${m.member?.displayName ?? m.author?.username ?? '?'}: ${parts}`;
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
/** 🔒-Button: Transcript verteilen, Thread löschen */
|
||||
export async function closeTicketWithTranscript(interaction) {
|
||||
const ticket = getTicket(interaction.channelId);
|
||||
if (!ticket) {
|
||||
await interaction.reply({ content: '❌ Das ist kein Ticket-Thread.', flags: MessageFlags.Ephemeral });
|
||||
return;
|
||||
}
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
closeTicket(interaction.channelId);
|
||||
|
||||
const threadName = interaction.channel.name ?? `ticket-${ticket.thread_id}`;
|
||||
const closedBy = interaction.member?.displayName ?? interaction.user.username;
|
||||
const transcript =
|
||||
`Ticket: ${threadName}\nGeschlossen von: ${closedBy}\n${'—'.repeat(40)}\n` +
|
||||
((await fetchTranscript(interaction.channel).catch(() => '')) || 'Keine Nachrichten.');
|
||||
const fileName = `ticket-${threadName.replace(/[^\wäöüß-]+/g, '-').replace(/^-+|-+$/g, '') || ticket.thread_id}.txt`;
|
||||
const makeFile = () => new AttachmentBuilder(Buffer.from(transcript, 'utf8'), { name: fileName });
|
||||
|
||||
// DM an den Ticket-Ersteller
|
||||
let dmOk = true;
|
||||
try {
|
||||
const user = await interaction.client.users.fetch(ticket.user_id);
|
||||
await user.send({
|
||||
content: `🔒 Dein Ticket **${threadName}** wurde geschlossen — hier dein Transcript. Danke für deine Anfrage!`,
|
||||
files: [makeFile()],
|
||||
});
|
||||
} catch {
|
||||
dmOk = false;
|
||||
}
|
||||
|
||||
// Kopie ins Mod-Log (Aufbewahrung fürs Team)
|
||||
const logChannelId = modlogChannelId();
|
||||
if (logChannelId) {
|
||||
const log = await interaction.client.channels.fetch(logChannelId).catch(() => null);
|
||||
await log?.send({
|
||||
embeds: [
|
||||
new EmbedBuilder()
|
||||
.setColor(brandColor())
|
||||
.setTitle(`🔒 Ticket geschlossen: ${threadName}`)
|
||||
.setDescription(`Ersteller: <@${ticket.user_id}> · geschlossen von **${closedBy}**${dmOk ? '' : '\n⚠️ Transcript-DM nicht zustellbar'}`)
|
||||
.setFooter({ text: brandFooter('SUPPORT') })
|
||||
.setTimestamp(),
|
||||
],
|
||||
files: [makeFile()],
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
await interaction.editReply(`🔒 Ticket geschlossen${dmOk ? ', Transcript per DM verschickt' : ' (DM blockiert — Transcript liegt im Mod-Log)'} — Thread wird gelöscht.`);
|
||||
await interaction.channel.delete().catch(async () => {
|
||||
// Löschen nicht erlaubt → wenigstens sperren + archivieren
|
||||
await interaction.channel.setLocked(true).catch(() => {});
|
||||
await interaction.channel.setArchived(true).catch(() => {});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user