Ticket-System, Web-Composer und Nachrichten-Bearbeitung
- Tickets: /ticket-setup postet 🎫-Button; Klick erstellt privaten Thread (ein offenes Ticket pro User), Schließen-Button sperrt + archiviert - Composer auf der Setup-Seite: Nachricht/Embed als Bot in beliebigen Kanal senden oder per Message-ID bearbeiten (nur eigene Bot-Posts) - API v1: PATCH /api/v1/message — Skripte können ihre Bot-Posts aktualisieren - Setting ticket_channel_id; README ergänzt Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+81
-3
@@ -1,11 +1,16 @@
|
||||
// Discord-Client: Commands laden, registrieren und Interactions verarbeiten
|
||||
import { Client, Collection, Events, GatewayIntentBits, MessageFlags, Partials, REST, Routes } from 'discord.js';
|
||||
import { config } from '../config.js';
|
||||
import { devlogChannelId, devlogPingRoleId, playtesterRoleId } from '../runtime-settings.js';
|
||||
import { devlogChannelId, devlogPingRoleId, playtesterRoleId, ticketChannelId } from '../runtime-settings.js';
|
||||
import { archiveDevlogMessage, removeDevlog } from './devlog-archive.js';
|
||||
import { registerCommunityListeners } from './community.js';
|
||||
import { registerModTools } from './mod-tools.js';
|
||||
import { addPlaytester, removePlaytester, isWish, bumpWish, getGiveaway, toggleGiveawayEntry, giveawayEntries } from '../db.js';
|
||||
import {
|
||||
addPlaytester, removePlaytester, isWish, bumpWish,
|
||||
getGiveaway, toggleGiveawayEntry, giveawayEntries,
|
||||
saveTicket, getTicket, closeTicket, openTicketOf,
|
||||
} from '../db.js';
|
||||
import { ChannelType, ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from 'discord.js';
|
||||
import * as ping from './commands/ping.js';
|
||||
import * as devlogBackfill from './commands/devlog-backfill.js';
|
||||
import * as bug from './commands/bug.js';
|
||||
@@ -13,9 +18,10 @@ import * as playtesterSetup from './commands/playtester-setup.js';
|
||||
import * as galerieBackfill from './commands/galerie-backfill.js';
|
||||
import * as wunsch from './commands/wunsch.js';
|
||||
import * as giveaway from './commands/giveaway.js';
|
||||
import * as ticketSetup from './commands/ticket-setup.js';
|
||||
|
||||
// Alle Commands hier eintragen — jedes Modul exportiert { data, execute }
|
||||
const commandModules = [ping, devlogBackfill, bug, playtesterSetup, galerieBackfill, wunsch, giveaway];
|
||||
const commandModules = [ping, devlogBackfill, bug, playtesterSetup, galerieBackfill, wunsch, giveaway, ticketSetup];
|
||||
|
||||
export async function startBot() {
|
||||
const client = new Client({
|
||||
@@ -117,6 +123,78 @@ export async function startBot() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 🎫-Button: Ticket erstellen (privater Thread) / schließen
|
||||
if (interaction.isButton() && interaction.customId === 'ticket_create') {
|
||||
try {
|
||||
const channelId = ticketChannelId();
|
||||
if (!channelId) throw new Error('Kein Ticket-Kanal konfiguriert');
|
||||
|
||||
// Pro User nur ein offenes Ticket
|
||||
const existing = openTicketOf(interaction.user.id);
|
||||
if (existing) {
|
||||
await interaction.reply({
|
||||
content: `❕ Du hast schon ein offenes Ticket: <#${existing.thread_id}>`,
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const channel = await interaction.client.channels.fetch(channelId);
|
||||
const thread = await channel.threads.create({
|
||||
name: `🎫 ${interaction.user.username}`,
|
||||
type: ChannelType.PrivateThread,
|
||||
invitable: false,
|
||||
autoArchiveDuration: 10080,
|
||||
});
|
||||
await thread.members.add(interaction.user.id);
|
||||
saveTicket(thread.id, interaction.user.id);
|
||||
|
||||
await thread.send({
|
||||
content: `<@${interaction.user.id}>`,
|
||||
embeds: [
|
||||
new EmbedBuilder()
|
||||
.setColor(0xf5c518)
|
||||
.setDescription(
|
||||
'Willkommen in deinem Ticket! Beschreib dein Anliegen — das Team meldet sich.\n' +
|
||||
'Wenn alles geklärt ist, kann das Ticket hier geschlossen werden.'
|
||||
)
|
||||
.setFooter({ text: 'D4RKST3R // SUPPORT' }),
|
||||
],
|
||||
components: [
|
||||
new ActionRowBuilder().addComponents(
|
||||
new ButtonBuilder().setCustomId('ticket_close').setStyle(ButtonStyle.Danger).setLabel('Ticket schließen').setEmoji('🔒')
|
||||
),
|
||||
],
|
||||
});
|
||||
await interaction.reply({
|
||||
content: `✅ Dein Ticket: <#${thread.id}>`,
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[ticket] Erstellung fehlgeschlagen:', error);
|
||||
await interaction
|
||||
.reply({ content: '❌ Ticket konnte nicht erstellt werden — dem Bot fehlen vermutlich Thread-Rechte.', flags: MessageFlags.Ephemeral })
|
||||
.catch(() => {});
|
||||
}
|
||||
return;
|
||||
}
|
||||
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(() => {});
|
||||
} catch (error) {
|
||||
console.error('[ticket] Schließen fehlgeschlagen:', error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 🎉-Button: Giveaway-Teilnahme togglen
|
||||
if (interaction.isButton() && interaction.customId === 'giveaway_enter') {
|
||||
const g = getGiveaway(interaction.message.id);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// /ticket-setup — postet das Ticket-Embed mit 🎫-Button in den aktuellen Kanal (Admin)
|
||||
import {
|
||||
SlashCommandBuilder, PermissionFlagsBits, MessageFlags,
|
||||
ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder,
|
||||
} from 'discord.js';
|
||||
import { ticketChannelId } from '../../runtime-settings.js';
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName('ticket-setup')
|
||||
.setDescription('Postet das Ticket-Embed mit Button in diesen Kanal')
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator);
|
||||
|
||||
export async function execute(interaction) {
|
||||
if (!ticketChannelId()) {
|
||||
await interaction.reply({
|
||||
content: '❌ Erst auf der Setup-Seite einen Ticket-Kanal wählen.',
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0xf5c518)
|
||||
.setTitle('🎫 Support-Ticket')
|
||||
.setDescription(
|
||||
'Du hast eine Frage, ein Problem oder ein privates Anliegen?\n\n' +
|
||||
'Klick auf den Button — es öffnet sich ein **privater Thread**, ' +
|
||||
'den nur du und das Team sehen können.'
|
||||
)
|
||||
.setFooter({ text: 'D4RKST3R // SUPPORT' });
|
||||
|
||||
const row = new ActionRowBuilder().addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId('ticket_create')
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setLabel('Ticket erstellen')
|
||||
.setEmoji('🎫')
|
||||
);
|
||||
|
||||
await interaction.channel.send({ embeds: [embed], components: [row] });
|
||||
await interaction.reply({ content: '✅ Ticket-Post erstellt.', flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
Reference in New Issue
Block a user