Bewerbungen zusätzlich per Discord-Webhook direkt in echte Kanäle posten

Ticketanizers Inbound-Ticket-API erzeugt bei API-Tickets nachweislich
keinen echten Discord-Kanal (nur Web-Inbox + Rollen-Ping im Log-Kanal,
auch nach manuellem Uebernehmen nicht) - das wurde heute gemeinsam mit
Dogi live in der Ticketanizer-Inbox und in Discord verifiziert. Deshalb
zusaetzlich ein normaler Discord-Webhook pro Zielkanal, der die komplette
Bewerbung garantiert sichtbar direkt postet.

Kanal-Zuordnung (von Dogi aus Discord kopiert):
  modi        -> #modi-bewerbung        (DISCORD_WEBHOOK_MODI)
  kooperation -> #kooperations-bewerbung (DISCORD_WEBHOOK_KOOPERATION)
  scout + creator -> #scout-bewerbung gemeinsam (DISCORD_WEBHOOK_SCOUT_MANAGER)

- Neu: cloudflare-worker/src/lib/discord-webhooks.js
- applications.js: postet jetzt parallel zu Ticketanizer (Promise.all,
  weiterhin ueber ctx.waitUntil im Hintergrund, blockiert nie die Antwort)
- Alle 3 Secrets bereits live gesetzt und deployed
- Getestet mit allen 4 Bewerbungstypen (modi/kooperation/scout/creator) -
  laut Log alle erfolgreich gepostet, Testdaten aus D1 wieder entfernt
- README.md: Einschraenkung von Ticketanizer dokumentiert + neue
  Webhook-Einrichtungsanleitung
This commit is contained in:
2026-08-01 14:27:08 +02:00
parent aa219922fd
commit 0d31fb8bb0
3 changed files with 170 additions and 5 deletions
@@ -0,0 +1,115 @@
/* =====================================================================
lib/discord-webhooks.js — Bewerbungen zusätzlich als ECHTE, sichtbare
Nachricht direkt in den passenden Discord-Kanal posten.
Hintergrund: Die Ticketanizer-Inbound-Ticket-API (siehe ticketanizer.js)
legt zwar zuverlässig ein Ticket in der Web-Inbox an und pingt eine Rolle
im Log-Kanal, erzeugt aber KEINEN echten, sichtbaren Ticket-Kanal in
Discord (das ist offenbar bewusstes Ticketanizer-Verhalten für
API-Tickets, siehe README). Deshalb zusätzlich hierüber ein stinknormaler
Discord-Webhook, der die komplette Bewerbung als Embed direkt in den
Zielkanal postet — 100% garantiert sichtbar, unabhängig von Ticketanizer.
Kanal-Zuordnung (von Dogi aus Discord kopiert, 2026-08-01):
modi → #modi-bewerbung
kooperation → #kooperations-bewerbung
scout → #scout-bewerbung (gemeinsam mit creator)
creator → #scout-bewerbung (gemeinsam mit scout — Wunsch von Dogi)
Secrets (siehe README.md):
DISCORD_WEBHOOK_MODI
DISCORD_WEBHOOK_KOOPERATION
DISCORD_WEBHOOK_SCOUT_MANAGER (für scout UND creator)
Rein informativ, kein Ausfallpunkt: Fehlt eine Webhook-URL, oder ist
Discord kurz nicht erreichbar, wird die Bewerbung trotzdem ganz normal
in der eigenen Datenbank gespeichert.
===================================================================== */
const WEBHOOK_ENV_KEYS = {
modi: "DISCORD_WEBHOOK_MODI",
kooperation: "DISCORD_WEBHOOK_KOOPERATION",
scout: "DISCORD_WEBHOOK_SCOUT_MANAGER",
creator: "DISCORD_WEBHOOK_SCOUT_MANAGER",
};
const TYPE_LABELS = {
modi: "🩵 Neue Bewerbung — Modi (Team Dogi)",
kooperation: "🤝 Neue Kooperationsanfrage",
scout: "🔎 Neue Bewerbung — Scout (Spicy Media)",
creator: "🎬 Neue Bewerbung — Creator / Manager-Bewerbung (Spicy Media)",
};
// Farbe je Bewerbungsart, passend zur Farbwelt auf der Website
// (babyblau = Dogfather/Team-Dogi-Community, lila = Kooperation, rot = Spicy Media).
const TYPE_COLORS = {
modi: 0x8fd9ea,
kooperation: 0x8b5cf6,
scout: 0xd94a5c,
creator: 0xd94a5c,
};
// Menschlich lesbare Feldbeschriftungen für alle Formularfelder, die aktuell
// auf der Website vorkommen (siehe bewerben-*.html, jeweils name="...").
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 bewerbungAlsDiscordNachrichtPosten(env, type, data) {
const envKey = WEBHOOK_ENV_KEYS[type];
const webhookUrl = envKey && env[envKey];
if (!webhookUrl) {
console.error(`[discord-webhook] Keine Webhook-URL für "${type}" hinterlegt (${envKey}) — übersprungen.`);
return;
}
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 {
const antwort = await fetch(webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ embeds: [embed] }),
});
if (!antwort.ok) {
const antwortText = await antwort.text().catch(() => "");
console.error(`[discord-webhook] Fehler beim Posten (${type}): HTTP ${antwort.status}${antwortText}`);
} else {
console.log(`[discord-webhook] Nachricht gepostet (${type}).`);
}
} catch (fehler) {
// Discord nicht erreichbar o.ä. — die Bewerbung selbst ist trotzdem
// sicher in der eigenen Datenbank gelandet, das darf hier nicht scheitern.
console.error(`[discord-webhook] Ausnahme beim Posten (${type}):`, fehler && fehler.message);
}
}