feat: initial Papo bot scaffold
This commit is contained in:
30
src/commands/admin/ban.ts
Normal file
30
src/commands/admin/ban.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('ban')
|
||||
.setDescription('Bannt einen Nutzer.')
|
||||
.addUserOption((opt) => opt.setName('user').setDescription('Nutzer').setRequired(true))
|
||||
.addStringOption((opt) => opt.setName('reason').setDescription('Grund'))
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const reason = interaction.options.getString('reason') ?? 'Kein Grund angegeben';
|
||||
if (!interaction.guild) return;
|
||||
|
||||
const member = await interaction.guild.members.fetch(user.id).catch(() => null);
|
||||
if (!member) {
|
||||
await interaction.reply({ content: 'Mitglied nicht gefunden.', ephemeral: true });
|
||||
return;
|
||||
}
|
||||
|
||||
await member.ban({ reason }).catch(() => null);
|
||||
await interaction.reply({ content: `${user.tag} wurde gebannt. Grund: ${reason}` });
|
||||
context.logging.logAction(user, 'Ban', reason);
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
22
src/commands/admin/clear.ts
Normal file
22
src/commands/admin/clear.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('clear')
|
||||
.setDescription('Löscht Nachrichten (max 100).')
|
||||
.addIntegerOption((opt) => opt.setName('amount').setDescription('Anzahl').setRequired(true))
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
const amount = interaction.options.getInteger('amount', true);
|
||||
if (!interaction.channel || amount < 1 || amount > 100) {
|
||||
await interaction.reply({ content: 'Anzahl muss zwischen 1 und 100 liegen.', ephemeral: true });
|
||||
return;
|
||||
}
|
||||
const messages = await interaction.channel.bulkDelete(amount, true);
|
||||
await interaction.reply({ content: `Gelöschte Nachrichten: ${messages.size}`, ephemeral: true });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
28
src/commands/admin/kick.ts
Normal file
28
src/commands/admin/kick.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('kick')
|
||||
.setDescription('Kickt einen Nutzer vom Server.')
|
||||
.addUserOption((opt) => opt.setName('user').setDescription('Nutzer').setRequired(true))
|
||||
.addStringOption((opt) => opt.setName('reason').setDescription('Grund'))
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.KickMembers),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (!interaction.guild) return;
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const reason = interaction.options.getString('reason') ?? 'Kein Grund angegeben';
|
||||
const member = await interaction.guild.members.fetch(user.id).catch(() => null);
|
||||
if (!member) {
|
||||
await interaction.reply({ content: 'Mitglied nicht gefunden.', ephemeral: true });
|
||||
return;
|
||||
}
|
||||
await member.kick(reason);
|
||||
await interaction.reply({ content: `${user.tag} wurde gekickt.` });
|
||||
context.logging.logAction(user, 'Kick', reason);
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
30
src/commands/admin/mute.ts
Normal file
30
src/commands/admin/mute.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('mute')
|
||||
.setDescription('Stummschalten per Timeout (Minuten).')
|
||||
.addUserOption((opt) => opt.setName('user').setDescription('Nutzer').setRequired(true))
|
||||
.addIntegerOption((opt) => opt.setName('minutes').setDescription('Dauer').setRequired(true))
|
||||
.addStringOption((opt) => opt.setName('reason').setDescription('Grund'))
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (!interaction.guild) return;
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const minutes = interaction.options.getInteger('minutes', true);
|
||||
const reason = interaction.options.getString('reason') ?? 'Kein Grund angegeben';
|
||||
const member = await interaction.guild.members.fetch(user.id).catch(() => null);
|
||||
if (!member) {
|
||||
await interaction.reply({ content: 'Mitglied nicht gefunden.', ephemeral: true });
|
||||
return;
|
||||
}
|
||||
await member.timeout(minutes * 60 * 1000, reason).catch(() => null);
|
||||
await interaction.reply({ content: `${user.tag} wurde für ${minutes} Minuten gemutet.` });
|
||||
context.logging.logAction(user, 'Mute', reason);
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
36
src/commands/admin/tempban.ts
Normal file
36
src/commands/admin/tempban.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('tempban')
|
||||
.setDescription('Bannt einen Nutzer temporär (Angabe in Minuten).')
|
||||
.addUserOption((opt) => opt.setName('user').setDescription('Nutzer').setRequired(true))
|
||||
.addIntegerOption((opt) => opt.setName('minutes').setDescription('Dauer').setRequired(true))
|
||||
.addStringOption((opt) => opt.setName('reason').setDescription('Grund'))
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (!interaction.guild) return;
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const minutes = interaction.options.getInteger('minutes', true);
|
||||
const reason = interaction.options.getString('reason') ?? 'Kein Grund angegeben';
|
||||
|
||||
const member = await interaction.guild.members.fetch(user.id).catch(() => null);
|
||||
if (!member) {
|
||||
await interaction.reply({ content: 'Mitglied nicht gefunden.', ephemeral: true });
|
||||
return;
|
||||
}
|
||||
|
||||
await member.ban({ reason: `${reason} | ${minutes} Minuten` });
|
||||
await interaction.reply({ content: `${user.tag} wurde für ${minutes} Minuten gebannt.` });
|
||||
context.logging.logAction(user, 'Tempban', reason);
|
||||
|
||||
setTimeout(async () => {
|
||||
await interaction.guild?.members.unban(user.id, 'Tempban abgelaufen').catch(() => null);
|
||||
}, minutes * 60 * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
30
src/commands/admin/timeout.ts
Normal file
30
src/commands/admin/timeout.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('timeout')
|
||||
.setDescription('Timeout in Minuten setzen.')
|
||||
.addUserOption((opt) => opt.setName('user').setDescription('Nutzer').setRequired(true))
|
||||
.addIntegerOption((opt) => opt.setName('minutes').setDescription('Dauer').setRequired(true))
|
||||
.addStringOption((opt) => opt.setName('reason').setDescription('Grund'))
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (!interaction.guild) return;
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const minutes = interaction.options.getInteger('minutes', true);
|
||||
const reason = interaction.options.getString('reason') ?? 'Kein Grund angegeben';
|
||||
const member = await interaction.guild.members.fetch(user.id).catch(() => null);
|
||||
if (!member) {
|
||||
await interaction.reply({ content: 'Mitglied nicht gefunden.', ephemeral: true });
|
||||
return;
|
||||
}
|
||||
await member.timeout(minutes * 60 * 1000, reason).catch(() => null);
|
||||
await interaction.reply({ content: `${user.tag} wurde für ${minutes} Minuten in Timeout gesetzt.` });
|
||||
context.logging.logAction(user, 'Timeout', reason);
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
26
src/commands/admin/unmute.ts
Normal file
26
src/commands/admin/unmute.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('unmute')
|
||||
.setDescription('Hebt einen Timeout auf.')
|
||||
.addUserOption((opt) => opt.setName('user').setDescription('Nutzer').setRequired(true))
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (!interaction.guild) return;
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const member = await interaction.guild.members.fetch(user.id).catch(() => null);
|
||||
if (!member) {
|
||||
await interaction.reply({ content: 'Mitglied nicht gefunden.', ephemeral: true });
|
||||
return;
|
||||
}
|
||||
await member.timeout(null).catch(() => null);
|
||||
await interaction.reply({ content: `${user.tag} ist nun entmuted.` });
|
||||
context.logging.logAction(user, 'Unmute');
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
30
src/commands/music/loop.ts
Normal file
30
src/commands/music/loop.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
import { LoopMode } from '../../services/musicService.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('loop')
|
||||
.setDescription('Loop-Modus setzen (off/song/queue).')
|
||||
.addStringOption((opt) =>
|
||||
opt
|
||||
.setName('mode')
|
||||
.setDescription('Loop Modus')
|
||||
.addChoices(
|
||||
{ name: 'aus', value: 'off' },
|
||||
{ name: 'song', value: 'song' },
|
||||
{ name: 'queue', value: 'queue' }
|
||||
)
|
||||
.setRequired(true)
|
||||
),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (!interaction.guildId) return;
|
||||
const mode = interaction.options.getString('mode', true) as LoopMode;
|
||||
context.music.setLoop(interaction.guildId, mode);
|
||||
await interaction.reply({ content: `Loop-Modus: ${mode}` });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
15
src/commands/music/pause.ts
Normal file
15
src/commands/music/pause.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder().setName('pause').setDescription('Pausiert die Wiedergabe.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (!interaction.guildId) return;
|
||||
context.music.pause(interaction.guildId);
|
||||
await interaction.reply({ content: 'Pausiert.' });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
17
src/commands/music/play.ts
Normal file
17
src/commands/music/play.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('play')
|
||||
.setDescription('Spielt einen Song oder fügt ihn zur Queue hinzu.')
|
||||
.addStringOption((opt) => opt.setName('query').setDescription('Link oder Suchbegriff').setRequired(true)),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
const query = interaction.options.getString('query', true);
|
||||
await context.music.play(interaction, query);
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
23
src/commands/music/queue.ts
Normal file
23
src/commands/music/queue.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction, EmbedBuilder } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder().setName('queue').setDescription('Zeigt die aktuelle Queue.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (!interaction.guildId) return;
|
||||
const info = context.music.getQueueInfo(interaction.guildId);
|
||||
if (!info) {
|
||||
await interaction.reply({ content: 'Keine Queue vorhanden.', ephemeral: true });
|
||||
return;
|
||||
}
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle('Aktuelle Queue')
|
||||
.addFields({ name: 'Jetzt', value: info.nowPlaying ? info.nowPlaying.title : 'Nichts' })
|
||||
.setDescription(info.upcoming.map((s, i) => `${i + 1}. ${s.title} — ${s.requester}`).join('\n') || 'Keine weiteren Songs');
|
||||
await interaction.reply({ embeds: [embed] });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
15
src/commands/music/resume.ts
Normal file
15
src/commands/music/resume.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder().setName('resume').setDescription('Setzt die Wiedergabe fort.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (!interaction.guildId) return;
|
||||
context.music.resume(interaction.guildId);
|
||||
await interaction.reply({ content: 'Weiter gehts.' });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
15
src/commands/music/skip.ts
Normal file
15
src/commands/music/skip.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder().setName('skip').setDescription('Überspringt den aktuellen Song.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (!interaction.guildId) return;
|
||||
context.music.skip(interaction.guildId);
|
||||
await interaction.reply({ content: 'Übersprungen.' });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
15
src/commands/music/stop.ts
Normal file
15
src/commands/music/stop.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder().setName('stop').setDescription('Stoppt die Wiedergabe und leert die Queue.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (!interaction.guildId) return;
|
||||
context.music.stop(interaction.guildId);
|
||||
await interaction.reply({ content: 'Musik gestoppt und Queue geleert.' });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
18
src/commands/tickets/claim.ts
Normal file
18
src/commands/tickets/claim.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder().setName('claim').setDescription('Übernimmt das aktuelle Ticket.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
const ok = await context.tickets.claimTicket(interaction);
|
||||
if (!ok) {
|
||||
await interaction.reply({ content: 'Kein Ticket in diesem Kanal gefunden.', ephemeral: true });
|
||||
return;
|
||||
}
|
||||
await interaction.reply({ content: 'Ticket übernommen.' });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
22
src/commands/tickets/close.ts
Normal file
22
src/commands/tickets/close.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('close')
|
||||
.setDescription('Schließt das aktuelle Ticket.')
|
||||
.addStringOption((opt) => opt.setName('reason').setDescription('Grund')),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
const reason = interaction.options.getString('reason') ?? undefined;
|
||||
const ok = await context.tickets.closeTicket(interaction, reason);
|
||||
if (!ok) {
|
||||
await interaction.reply({ content: 'Kein Ticket in diesem Kanal gefunden.', ephemeral: true });
|
||||
return;
|
||||
}
|
||||
await interaction.reply({ content: 'Ticket geschlossen.' });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
26
src/commands/tickets/panel.ts
Normal file
26
src/commands/tickets/panel.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction, PermissionFlagsBits, ChannelType, TextChannel } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('ticketpanel')
|
||||
.setDescription('Sendet ein Ticket-Panel in einen Kanal.')
|
||||
.addChannelOption((opt) =>
|
||||
opt.setName('channel').setDescription('Zielkanal').addChannelTypes(ChannelType.GuildText)
|
||||
)
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
const target = (interaction.options.getChannel('channel') as TextChannel | null) ?? interaction.channel;
|
||||
if (!target || !target.isTextBased()) {
|
||||
await interaction.reply({ content: 'Bitte wähle einen Textkanal.', ephemeral: true });
|
||||
return;
|
||||
}
|
||||
const { embed, buttons } = context.tickets.buildPanelEmbed();
|
||||
await target.send({ embeds: [embed], components: [buttons] });
|
||||
await interaction.reply({ content: `Ticket-Panel gesendet in ${target}`, ephemeral: true });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
34
src/commands/tickets/priority.ts
Normal file
34
src/commands/tickets/priority.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { prisma } from '../../database/index.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('ticketpriority')
|
||||
.setDescription('Setzt die Priorität des aktuellen Tickets.')
|
||||
.addStringOption((opt) =>
|
||||
opt
|
||||
.setName('level')
|
||||
.setDescription('Priorität')
|
||||
.addChoices(
|
||||
{ name: 'Low', value: 'low' },
|
||||
{ name: 'Normal', value: 'normal' },
|
||||
{ name: 'High', value: 'high' }
|
||||
)
|
||||
.setRequired(true)
|
||||
),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
const channelId = interaction.channelId;
|
||||
const ticket = await prisma.ticket.findFirst({ where: { channelId } });
|
||||
if (!ticket) {
|
||||
await interaction.reply({ content: 'Kein Ticket in diesem Kanal.', ephemeral: true });
|
||||
return;
|
||||
}
|
||||
const level = interaction.options.getString('level', true) as 'low' | 'normal' | 'high';
|
||||
await prisma.ticket.update({ where: { id: ticket.id }, data: { priority: level } });
|
||||
await interaction.reply({ content: `Ticket-Priorität gesetzt auf **${level}**.` });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
33
src/commands/tickets/status.ts
Normal file
33
src/commands/tickets/status.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { prisma } from '../../database/index.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('ticketstatus')
|
||||
.setDescription('Ändert den Status des aktuellen Tickets.')
|
||||
.addStringOption((opt) =>
|
||||
opt
|
||||
.setName('status')
|
||||
.setDescription('Neuer Status')
|
||||
.addChoices(
|
||||
{ name: 'Offen', value: 'open' },
|
||||
{ name: 'In Bearbeitung', value: 'in-progress' },
|
||||
{ name: 'Geschlossen', value: 'closed' }
|
||||
)
|
||||
.setRequired(true)
|
||||
),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
const status = interaction.options.getString('status', true) as 'open' | 'in-progress' | 'closed';
|
||||
const ticket = await prisma.ticket.findFirst({ where: { channelId: interaction.channelId } });
|
||||
if (!ticket) {
|
||||
await interaction.reply({ content: 'Kein Ticket in diesem Kanal.', ephemeral: true });
|
||||
return;
|
||||
}
|
||||
await prisma.ticket.update({ where: { id: ticket.id }, data: { status } });
|
||||
await interaction.reply({ content: `Ticket-Status geändert zu **${status}**.` });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
22
src/commands/tickets/ticket.ts
Normal file
22
src/commands/tickets/ticket.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder().setName('ticket').setDescription('Erstellt ein persönliches Ticket.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
const ticket = await context.tickets.createTicket(interaction);
|
||||
if (!ticket) {
|
||||
if (!interaction.replied) {
|
||||
await interaction.reply({ content: 'Ticket konnte nicht erstellt werden.', ephemeral: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!interaction.replied) {
|
||||
await interaction.reply({ content: 'Ticket erstellt! Schau in deinem neuen Kanal nach.', ephemeral: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
25
src/commands/tickets/transcript.ts
Normal file
25
src/commands/tickets/transcript.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
import { prisma } from '../../database/index.js';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder().setName('transcript').setDescription('Exportiert das Transcript dieses Tickets.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
const channelId = interaction.channelId;
|
||||
const ticket = await prisma.ticket.findFirst({ where: { channelId } });
|
||||
if (!ticket) {
|
||||
await interaction.reply({ content: 'Kein Ticket in diesem Kanal.', ephemeral: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const filePath = await context.tickets.exportTranscript(interaction.channel!, ticket.id);
|
||||
const fileName = path.basename(filePath);
|
||||
await interaction.reply({ content: `Transcript exportiert: ${fileName}`, files: [fs.createReadStream(filePath)] });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
36
src/commands/utility/configure.ts
Normal file
36
src/commands/utility/configure.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction, PermissionFlagsBits, ChannelType } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { settings } from '../../config/state.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('configure')
|
||||
.setDescription('Setzt Basis-Einstellungen des Bots (gildenspezifisch).')
|
||||
.addChannelOption((opt) => opt.setName('welcome_channel').setDescription('Kanal für Willkommensnachrichten').addChannelTypes(ChannelType.GuildText))
|
||||
.addChannelOption((opt) => opt.setName('log_channel').setDescription('Kanal für Logs').addChannelTypes(ChannelType.GuildText))
|
||||
.addBooleanOption((opt) => opt.setName('automod').setDescription('Automod an/aus'))
|
||||
.addBooleanOption((opt) => opt.setName('leveling').setDescription('Level-System an/aus'))
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (!interaction.guildId) return;
|
||||
const guildSetting = settings.get(interaction.guildId) ?? {};
|
||||
const welcome = interaction.options.getChannel('welcome_channel');
|
||||
const logChannel = interaction.options.getChannel('log_channel');
|
||||
const automod = interaction.options.getBoolean('automod');
|
||||
const leveling = interaction.options.getBoolean('leveling');
|
||||
|
||||
if (welcome) guildSetting.welcomeChannelId = welcome.id;
|
||||
if (logChannel) guildSetting.logChannelId = logChannel.id;
|
||||
if (automod !== null) guildSetting.automodEnabled = automod;
|
||||
if (leveling !== null) guildSetting.levelingEnabled = leveling;
|
||||
|
||||
settings.set(interaction.guildId, guildSetting);
|
||||
context.logging = new (context.logging.constructor as any)(guildSetting.logChannelId);
|
||||
|
||||
await interaction.reply({ content: 'Einstellungen gespeichert.', ephemeral: true });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
20
src/commands/utility/help.ts
Normal file
20
src/commands/utility/help.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction, EmbedBuilder } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
data: new SlashCommandBuilder().setName('help').setDescription('Zeigt Befehle und Module.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle('Papo Hilfe')
|
||||
.setDescription('Multi-Guild ready | Admin, Tickets, Musik, Automod, Dashboard')
|
||||
.addFields(
|
||||
{ name: 'Admin', value: '/ban /kick /mute /timeout /clear', inline: false },
|
||||
{ name: 'Tickets', value: '/ticket /ticketpanel /ticketpriority /ticketstatus /transcript', inline: false },
|
||||
{ name: 'Musik', value: '/play /pause /resume /skip /stop /queue /loop', inline: false },
|
||||
{ name: 'Utility', value: '/ping /configure /serverinfo /rank', inline: false }
|
||||
);
|
||||
await interaction.reply({ embeds: [embed], ephemeral: true });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
11
src/commands/utility/ping.ts
Normal file
11
src/commands/utility/ping.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
data: new SlashCommandBuilder().setName('ping').setDescription('Antwortet mit Pong!'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
await interaction.reply({ content: 'Pong!' });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
19
src/commands/utility/rank.ts
Normal file
19
src/commands/utility/rank.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
import { context } from '../../config/context.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('rank')
|
||||
.setDescription('Zeigt deinen XP-Status an.')
|
||||
.addUserOption((opt) => opt.setName('user').setDescription('Nutzer (optional)')),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (!interaction.guildId) return;
|
||||
const user = interaction.options.getUser('user') ?? interaction.user;
|
||||
const level = context.leveling.getLevel(user.id, interaction.guildId);
|
||||
await interaction.reply({ content: `${user.tag}: Level ${level.level}, XP ${level.xp}` });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
21
src/commands/utility/serverinfo.ts
Normal file
21
src/commands/utility/serverinfo.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction, EmbedBuilder } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types.js';
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder().setName('serverinfo').setDescription('Zeigt Infos zum aktuellen Server.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
const guild = interaction.guild!;
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(guild.name)
|
||||
.setThumbnail(guild.iconURL() ?? null)
|
||||
.addFields(
|
||||
{ name: 'Mitglieder', value: `${guild.memberCount}`, inline: true },
|
||||
{ name: 'Erstellt', value: `<t:${Math.floor(guild.createdTimestamp / 1000)}:R>`, inline: true },
|
||||
{ name: 'ID', value: guild.id, inline: true }
|
||||
);
|
||||
await interaction.reply({ embeds: [embed] });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
Reference in New Issue
Block a user