Bewerbungen als echte Tickets bei Ticketanizer anlegen (statt Discord-Webhook)
Umgestellt von der ursprünglichen Discord-Webhook-Idee auf die offizielle Ticketanizer-Inbound-Ticket-API (api.ticketanizer.com/v1/tickets) - erzeugt echte Tickets statt nur Nachrichten, landen dadurch direkt im richtigen Ticketanizer-Bereich in Discord. - Neu: cloudflare-worker/src/lib/ticketanizer.js. panel_id pro Bewerbungs- bereich fest hinterlegt (aus dem Ticketanizer-Dashboard abgelesen): Modi=4, Kooperation=5, Scout=6, Creator=7 (dort "Manager-Bewerbung") - Alte discord.js entfernt (Ansatz verworfen) - applications.js: Aufruf entsprechend umbenannt/angepasst - README.md: Einrichtungsanleitung aktualisiert (TICKETANIZER_API_KEY statt 4x DISCORD_WEBHOOK_*) - Ticketanizer bleibt rein informativ, kein Ausfallpunkt - Bewerbung wird immer in der eigenen Datenbank gespeichert, unabhängig vom Ticket-Erfolg - Secret TICKETANIZER_API_KEY bereits live gesetzt und deployed; End-to- End mit 3 Test-Bewerbungen geprüft (HTTP Ok geloggt), Testeinträge aus der DB wieder entfernt
This commit is contained in:
@@ -1,97 +0,0 @@
|
||||
/* =====================================================================
|
||||
lib/discord.js — Bewerbungen als Discord-Webhook-Nachricht weiterleiten
|
||||
(Ticketanizer-Kanäle auf dem Discord).
|
||||
|
||||
Rein optional und niemals ein Ausfallpunkt: Fehlt die Webhook-URL für
|
||||
einen Bewerbungstyp, oder schlägt der Versand an Discord fehl (Discord
|
||||
down, falsche URL, ...), wird die Bewerbung trotzdem ganz normal in der
|
||||
Datenbank gespeichert — Discord ist nur ein zusätzlicher Kanal, kein
|
||||
Ausfallpunkt für die eigentliche Bewerbung.
|
||||
|
||||
Pro Bewerbungsart eine eigene Webhook-URL, als Secret gesetzt (NICHT in
|
||||
wrangler.toml/git, siehe README.md):
|
||||
wrangler secret put DISCORD_WEBHOOK_MODI
|
||||
wrangler secret put DISCORD_WEBHOOK_SCOUT
|
||||
wrangler secret put DISCORD_WEBHOOK_CREATOR
|
||||
wrangler secret put DISCORD_WEBHOOK_KOOPERATION
|
||||
===================================================================== */
|
||||
|
||||
const WEBHOOK_ENV_KEYS = {
|
||||
modi: "DISCORD_WEBHOOK_MODI",
|
||||
scout: "DISCORD_WEBHOOK_SCOUT",
|
||||
creator: "DISCORD_WEBHOOK_CREATOR",
|
||||
kooperation: "DISCORD_WEBHOOK_KOOPERATION",
|
||||
};
|
||||
|
||||
const TYPE_LABELS = {
|
||||
modi: "🩵 Neue Bewerbung — Modi (Team Dogi)",
|
||||
scout: "🔎 Neue Bewerbung — Scout (Spicy Media)",
|
||||
creator: "🎬 Neue Bewerbung — Creator (Spicy Media)",
|
||||
kooperation: "🤝 Neue Kooperationsanfrage",
|
||||
};
|
||||
|
||||
// Farbe je Bewerbungsart, passend zur Farbwelt auf der Website
|
||||
// (babyblau = Dogfather/Team-Dogi-Community, rot = Spicy Media, lila = Kooperation).
|
||||
const TYPE_COLORS = {
|
||||
modi: 0x8fd9ea,
|
||||
scout: 0xd94a5c,
|
||||
creator: 0xd94a5c,
|
||||
kooperation: 0x8b5cf6,
|
||||
};
|
||||
|
||||
// Menschlich lesbare Feldbeschriftungen für alle Formularfelder, die aktuell
|
||||
// auf der Website vorkommen (siehe bewerben-*.html, jeweils name="...").
|
||||
// Unbekannte Feldnamen (z.B. nach künftigen Formular-Änderungen) werden
|
||||
// einfach unverändert als Beschriftung übernommen statt zu fehlen.
|
||||
const FIELD_LABELS = {
|
||||
name: "Name",
|
||||
email: "E-Mail",
|
||||
kontakt: "Kontakt (Discord/TikTok)",
|
||||
erfahrung: "Erfahrung",
|
||||
verfuegbarkeit: "Verfügbarkeit",
|
||||
warum: "Warum möchtest du dabei sein?",
|
||||
platform: "Plattform",
|
||||
content: "Content-Art",
|
||||
firma: "Firma / Projekt",
|
||||
idee: "Idee / Anliegen",
|
||||
};
|
||||
|
||||
function feldWert(wert) {
|
||||
const text = String(wert ?? "").trim();
|
||||
if (!text) return "_(leer)_";
|
||||
// Discord-Embed-Feldwerte sind auf 1024 Zeichen begrenzt.
|
||||
return text.length > 1000 ? text.slice(0, 1000) + "…" : text;
|
||||
}
|
||||
|
||||
export async function bewerbungAnDiscordSenden(env, type, data) {
|
||||
const envKey = WEBHOOK_ENV_KEYS[type];
|
||||
const webhookUrl = envKey && env[envKey];
|
||||
if (!webhookUrl) return; // kein Webhook für diesen Bereich eingerichtet — einfach überspringen
|
||||
|
||||
const fields = Object.entries(data || {})
|
||||
.filter(([, v]) => String(v ?? "").trim() !== "")
|
||||
.map(([key, value]) => ({
|
||||
name: FIELD_LABELS[key] || key,
|
||||
value: feldWert(value),
|
||||
inline: String(value).length < 40,
|
||||
}));
|
||||
|
||||
const embed = {
|
||||
title: TYPE_LABELS[type] || "Neue Bewerbung",
|
||||
color: TYPE_COLORS[type] ?? 0x8fd9ea,
|
||||
fields: fields.length ? fields : [{ name: "Hinweis", value: "Keine Angaben übermittelt." }],
|
||||
footer: { text: "DOGFATHER UNIVERSE — automatisch von der Homepage" },
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
try {
|
||||
await fetch(webhookUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ embeds: [embed] }),
|
||||
});
|
||||
} catch {
|
||||
// Discord nicht erreichbar o.ä. — die Bewerbung selbst ist trotzdem
|
||||
// sicher in der Datenbank gelandet, das darf hier nicht scheitern.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/* =====================================================================
|
||||
lib/ticketanizer.js — Bewerbungen als echtes Ticket bei Ticketanizer
|
||||
eröffnen (dort in Discord sichtbar, pro Bewerbungsbereich ein eigenes
|
||||
Panel/Board).
|
||||
|
||||
API-Doku (aus dem Ticketanizer-Dashboard, "API & Webhooks" → "API-Keys" →
|
||||
"So nutzt du sie"):
|
||||
Basis-URL: https://api.ticketanizer.com
|
||||
POST /v1/tickets
|
||||
Header: X-API-Key, Content-Type: application/json
|
||||
Body: { panel_id, visitor_name, first_message }
|
||||
|
||||
Wir nutzen hier bewusst NUR den ersten Schritt (Ticket eröffnen) — es
|
||||
ist eine einmalige Bewerbung, kein Live-Chat, deshalb kein Nachladen
|
||||
von Staff-Antworten nötig (das würde einen dauerhaft laufenden Prozess
|
||||
brauchen, den ein Cloudflare Worker so nicht hat).
|
||||
|
||||
panel_id pro Bewerbungsbereich (aus dem Ticketanizer-Dashboard →
|
||||
"Panels", von Dogi am 2026-08-01 abgelesen):
|
||||
4 = Modi-Bewerbung
|
||||
5 = Kooperationsbewerbung
|
||||
6 = Scout-Bewerbung
|
||||
7 = Manager-Bewerbung (= unsere "Creator"-Bewerbung: Streamer sucht
|
||||
einen Manager bei Spicy Media)
|
||||
|
||||
Rein informativ, kein Ausfallpunkt: Fehlt der API-Key, oder schlägt der
|
||||
Versand fehl (Ticketanizer down, falscher Key, ...), wird die Bewerbung
|
||||
trotzdem ganz normal in der eigenen Datenbank gespeichert.
|
||||
===================================================================== */
|
||||
|
||||
const TICKETANIZER_BASIS_URL = "https://api.ticketanizer.com";
|
||||
|
||||
const PANEL_IDS = {
|
||||
modi: 4,
|
||||
kooperation: 5,
|
||||
scout: 6,
|
||||
creator: 7, // "Manager-Bewerbung" bei Ticketanizer
|
||||
};
|
||||
|
||||
// Menschlich lesbare Feldbeschriftungen für alle Formularfelder, die aktuell
|
||||
// auf der Website vorkommen (siehe bewerben-*.html, jeweils name="...").
|
||||
// Unbekannte Feldnamen (z.B. nach künftigen Formular-Änderungen) werden
|
||||
// einfach unverändert als Beschriftung übernommen statt zu fehlen.
|
||||
const FIELD_LABELS = {
|
||||
name: "Name",
|
||||
email: "E-Mail",
|
||||
kontakt: "Kontakt (Discord/TikTok)",
|
||||
erfahrung: "Erfahrung",
|
||||
verfuegbarkeit: "Verfügbarkeit",
|
||||
warum: "Warum möchtest du dabei sein?",
|
||||
platform: "Plattform",
|
||||
content: "Content-Art",
|
||||
firma: "Firma / Projekt",
|
||||
idee: "Idee / Anliegen",
|
||||
};
|
||||
|
||||
function ersteNachrichtAufbauen(type, data) {
|
||||
const zeilen = Object.entries(data || {})
|
||||
.filter(([, v]) => String(v ?? "").trim() !== "")
|
||||
.map(([key, value]) => `**${FIELD_LABELS[key] || key}:** ${String(value).trim()}`);
|
||||
|
||||
if (!zeilen.length) zeilen.push("_Keine weiteren Angaben übermittelt._");
|
||||
|
||||
// Ticketanizer-Nachrichten sind vermutlich nicht beliebig lang — sicherheitshalber
|
||||
// deckeln, damit ein sehr langer Freitext (z.B. "warum") den Versand nicht sprengt.
|
||||
const text = zeilen.join("\n");
|
||||
return text.length > 3500 ? text.slice(0, 3500) + "…" : text;
|
||||
}
|
||||
|
||||
export async function bewerbungAlsTicketAnlegen(env, type, data) {
|
||||
const apiKey = env.TICKETANIZER_API_KEY;
|
||||
const panelId = PANEL_IDS[type];
|
||||
if (!apiKey || !panelId) return; // nicht eingerichtet für diesen Bereich — einfach überspringen
|
||||
|
||||
const visitorName = String(data?.name || data?.firma || "Unbekannt").trim().slice(0, 80) || "Unbekannt";
|
||||
const firstMessage = ersteNachrichtAufbauen(type, data);
|
||||
|
||||
try {
|
||||
await fetch(`${TICKETANIZER_BASIS_URL}/v1/tickets`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-API-Key": apiKey,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
panel_id: panelId,
|
||||
visitor_name: visitorName,
|
||||
first_message: firstMessage,
|
||||
}),
|
||||
});
|
||||
} catch {
|
||||
// Ticketanizer nicht erreichbar o.ä. — die Bewerbung selbst ist trotzdem
|
||||
// sicher in der eigenen Datenbank gelandet, das darf hier nicht scheitern.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user