feat(discord): Ticket-Threads mit bidirektionaler Synchronisierung

Zweiter Teil des Discord-Ausbaus. Hat ein Kunde sein Konto verknüpft
(siehe vorheriger Commit), bekommt sein Ticket automatisch einen
privaten Thread im konfigurierten Ticket-Kanal:

- Neuer Job discord.ticket_sync: legt den Thread beim ersten Bedarf
  an (Name "ticket-<Nummer>", Kunde wird als Mitglied hinzugefügt)
  und postet dort jede neue, nicht-interne Nachricht (sowohl Kunden-
  als auch Personal-Antworten). Wird von /tickets (Erstellung durch
  Kunden) und /tickets/:id/messages (Antworten, keine internen
  Notizen) ausgelöst.
- Umgekehrte Richtung: Antwortet der Kunde direkt im Thread, übernimmt
  ein neuer messageCreate-Listener im Bot die Nachricht als
  Ticket-Antwort – nur wenn der Absender über discord_user_id
  eindeutig als Mitglied der Organisation des Tickets verifiziert ist.
  Personal-Nachrichten im Thread werden bewusst nicht übernommen (Web-
  Oberfläche bleibt die Quelle für Personal-Antworten, kein Echo).
- Dafür nötig: privilegierte Message Content Intent (war in der
  Anleitung aus dem vorigen Schritt schon vermerkt).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kundencenter 2026-09-28 23:44:53 +02:00
parent fcbd34b0c6
commit 1cd1a8b03a
3 changed files with 54 additions and 5 deletions

View file

@ -1,5 +1,5 @@
import { pool } from '@kc/platform/db';
import { notifyAdmin } from './discord.js';
import { notifyAdmin, postTicketMessage } from './discord.js';
import { JobFailure } from '@kc/platform/jobs';
import { sendTemplateMail } from '@kc/platform/mail';
import { executeAction, executeChild, provisionOrder, syncInstance, type ChildPayload, type ExecutePayload } from '@kc/connectors';
@ -26,6 +26,9 @@ const handlers: Record<string, Handler> = {
if (status === 'no_template') throw new Error(`Unbekannte Mailvorlage: ${p.key}`);
return status === 'not_configured' ? 'übersprungen: SMTP nicht konfiguriert' : status === 'disabled' ? 'übersprungen: Vorlage deaktiviert' : 'gesendet';
},
'discord.ticket_sync': async (p: { ticketId: string; body: string; authorLabel: string }) => {
await postTicketMessage(p.ticketId, p.body, p.authorLabel);
},
};
handlers['connector.sync'] = async (p, j) => {
const r = await syncInstance(p.instanceId, j.correlationId);