import { prisma } from '../database'; import { context } from '../config/context'; import { TextChannel } from 'discord.js'; type AutomationCondition = { category?: string; status?: string; minHours?: number; }; type AutomationAction = { type: 'pingRole' | 'reminder' | 'flag'; roleId?: string; message?: string; status?: string; }; export class TicketAutomationService { public async list(guildId: string) { return prisma.ticketAutomationRule.findMany({ where: { guildId }, orderBy: { createdAt: 'asc' } }); } public async save(rule: { id?: string; guildId: string; name: string; condition: AutomationCondition; action: AutomationAction; active?: boolean; }) { if (rule.id) { return prisma.ticketAutomationRule.update({ where: { id: rule.id }, data: { name: rule.name, condition: rule.condition, action: rule.action, active: rule.active ?? true } }); } return prisma.ticketAutomationRule.create({ data: { guildId: rule.guildId, name: rule.name, condition: rule.condition, action: rule.action, active: rule.active ?? true } }); } public async remove(guildId: string, id: string) { const found = await prisma.ticketAutomationRule.findFirst({ where: { id, guildId } }); if (!found) return false; await prisma.ticketAutomationRule.delete({ where: { id } }); return true; } public async checkTicket(ticket: any, isScheduled = false) { const rules = await prisma.ticketAutomationRule.findMany({ where: { guildId: ticket.guildId, active: true }, take: 50 }); if (!rules.length) return; const guild = context.client?.guilds.cache.get(ticket.guildId) ?? (await context.client?.guilds.fetch(ticket.guildId).catch(() => null)); if (!guild) return; const channel = ticket.channelId ? await guild.channels.fetch(ticket.channelId).catch(() => null) : null; for (const rule of rules) { const cond = (rule.condition as any) || {}; const act = (rule.action as any) || {}; const matchesCategory = !cond.category || (ticket.topic || '').toLowerCase().includes(String(cond.category).toLowerCase()); const matchesStatus = !cond.status || ticket.status === cond.status; const matchesAge = !cond.minHours || (ticket.createdAt && Date.now() - new Date(ticket.createdAt).getTime() >= Number(cond.minHours) * 3600 * 1000); if (!matchesCategory || !matchesStatus || !matchesAge) continue; if (act.type === 'pingRole' && channel?.isTextBased() && act.roleId) { await (channel as TextChannel).send({ content: `<@&${act.roleId}> Bitte Ticket pruefen.` }).catch(() => undefined); } if (act.type === 'reminder' && channel?.isTextBased()) { await (channel as TextChannel) .send({ content: act.message || 'Reminder: Ticket ist noch offen.' }) .catch(() => undefined); } if (act.type === 'flag' && act.status && ticket.status !== act.status) { await prisma.ticket.update({ where: { id: ticket.id }, data: { status: act.status } }).catch(() => undefined); } } } public startLoop() { setInterval(() => { const since = new Date(Date.now() - 24 * 60 * 60 * 1000); prisma.ticket .findMany({ where: { status: { notIn: ['erledigt', 'closed'] }, createdAt: { lte: since } }, take: 50 }) .then((tickets) => tickets.forEach((t) => this.checkTicket(t, true))) .catch(() => undefined); }, 60_000); } }