Files
Papo/src/services/ticketAutomationService.ts
Pascal Prießnitz 5bf42f4610
All checks were successful
Deploy Discord Bot / deploy (push) Successful in 36s
[deploy] add ticket sla, pipeline, automations, kb
2025-12-03 13:24:25 +01:00

104 lines
3.6 KiB
TypeScript

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);
}
}